1.全局组件 #

Vue.js 的组件是一种自定义元素,它们在技术上是 Vue 的实例,具有预定义的选项。组件是 Vue.js 的核心功能,因为它们允许你创建可重用的代码片段。

组件系统可以让你创建具有完全独立或者有深度复用的组件树,也就是说你可以创建一个复杂的页面,其中各部分是由相互关联的小组件构成的。这样可以提高代码的可维护性和可重用性。

全局组件在 Vue.js 中是很常见的,一旦定义,你就可以在任何 Vue 实例(也包括组件)的模板中使用它。

你可以使用 Vue.component() 方法在全局范围内注册一个组件。这个方法接收两个参数:组件的名字和组件的选项。

以下是创建全局组件的例子:

  <body>
    <div id="app">
      <counter></counter>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script>
      Vue.component("counter", {
        data: function () {
          return {
            count: 0,
          };
        },
        template:
          '<button v-on:click="count++">{{ count }}</button>',
      });
      var vm = new Vue({
        el: "#app",
        data: {},
        methods: {},
      });
    </script>
  </body>

注意,全局注册的组件必须在注册之后的 Vue 实例(通过 new Vue 创建)中才可用。

虽然全局组件在很多情况下都很有用,但如果你的应用或网站有大量的组件,全局注册可能会导致命名冲突

2.局部组件 #

在 Vue.js 中,除了全局注册的组件,还可以创建局部组件。局部组件只能在声明它们的组件或 Vue 实例中使用,而不是在全局可用。

局部组件的创建和全局组件略有不同。你不再使用 Vue.component() 方法来注册组件,而是在另一个组件的选项中定义它,如下所示:

  <body>
    <div id="app">
      <counter></counter>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script>
      var vm = new Vue({
        el: "#app",
        components: {
          counter: {
            data: function () {
              return {
                count: 0,
              };
            },
            template: '<button v-on:click="count++">{{ count }}</button>',
          },
        },
      });
    </script>
  </body>

局部注册的好处是,你不需要担心在全局命名冲突的问题,因为每个组件都有自己的作用域。

3.子组件生命周期 #

Vue.js中父子组件生命周期钩子的执行流程主要遵循如下步骤:

在创建阶段:

  1. beforeCreate: 首先,父组件的beforeCreate钩子方法会被触发。

  2. created: 其次,父组件的created钩子方法会被触发。

  3. beforeCreate: 然后,子组件的beforeCreate钩子方法会被触发。

  4. created: 接着,子组件的created钩子方法会被触发。

  5. beforeMount: 之后,父组件的beforeMount钩子方法会被触发。

  6. beforeMount: 接着,子组件的beforeMount钩子方法会被触发。

  7. mounted: 接下来,子组件的mounted钩子方法会被触发。

  8. mounted: 最后,父组件的mounted钩子方法会被触发。

在更新阶段:

  1. beforeUpdate: 首先,父组件的beforeUpdate钩子方法会被触发。

  2. beforeUpdate: 然后,子组件的beforeUpdate钩子方法会被触发。

  3. updated: 接着,子组件的updated钩子方法会被触发。

  4. updated: 最后,父组件的updated钩子方法会被触发。

在销毁阶段:

  1. beforeDestroy: 首先,父组件的beforeDestroy钩子方法会被触发。

  2. beforeDestroy: 然后,子组件的beforeDestroy钩子方法会被触发。

  3. destroyed: 接着,子组件的destroyed钩子方法会被触发。

  4. destroyed: 最后,父组件的destroyed钩子方法会被触发。

这种顺序的设置使得父组件能在它的钩子中控制子组件的生命周期,这是Vue.js设计的一部分。在实际的开发中,理解和利用好这些生命周期钩子可以帮助我们更好地管理组件的行为和状态。

4.父组件向子组件传值 #

在 Vue.js 中,父组件可以使用 props 向子组件传递数据。这是单向数据流,数据的所有权和修改权仍在父组件中,下面是一个详细的流程:

  1. 定义子组件,并在子组件中声明 props:首先,我们需要创建一个子组件,并在这个子组件中声明我们想要接收的 prop。这可以通过在子组件中定义一个 props 选项来实现。
  2. 在父组件中绑定数据到子组件的 props:然后,在父组件中,我们可以将数据绑定到子组件的 props 上。这可以通过在子组件的 HTML 标签上使用 v-bind 指令(或简写为 :)来实现。
  3. 在父组件的数据中定义要传递的数据:在父组件的数据选项中,我们需要定义要传递给子组件的数据。

这就是父组件向子组件传递数据的基本流程。值得注意的是,这种数据流是单向的,也就是说子组件不能直接修改从父组件接收的 prop 的值。如果子组件需要根据父组件传递的 prop 来修改自己的数据,那么它应该将这个 prop 的值赋给一个本地的数据属性,然后进行修改。

<body>
    <div id="app">
        <counter :count="count"></counter>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                count:5
            },
            components: {
                'counter': {
                     //props: ['count'],
                    props: {
                      count:String
                    },
                    template: `
                    <button v-on:click="count++">
                        {{count}}
                    </button>`
                }
            }
        })
    </script>
</body>

请注意,由于 JavaScript 的限制,Vue 不能检测到对象属性的添加或删除,所以尽量在创建实例时,就包含所有必要的字段和初始值。

这就是 Vue.js 中父子组件之间数据通信的基础。对于更复杂的情况,你可能需要使用 Vue 的自定义事件系统,或者更高级的状态管理模式,如 Vuex。

在 Vue.js 中,你可以在组件的 props 选项中进行类型校验。这可以确保你的组件以正确的方式使用,并在开发过程中避免一些常见的错误。Vue.js 支持多种 JavaScript 原始类型的校验,包括字符串、数字、布尔值、数组和对象,甚至可以使用自定义的验证函数。

以下是一些基本的类型校验示例:

Vue.component('my-component', {
  props: {
    // 基本类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多种类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数返回
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

上述代码片段展示了多种 props 校验方式,包括基本类型校验,可选类型,必填字段,字段默认值,以及自定义验证函数。

这种方式能让我们在组件被错误使用时提供更友好的警告。尤其是在开发过程中,这种类型校验可以帮助我们发现和避免错误。

5.子组件向父组件传递数据 #

在 Vue 中,子组件向父组件传递数据通常使用自定义事件的方式。下面是一个基本的步骤:

  1. 首先,在子组件中我们需要使用 $emit 方法触发一个自定义事件,并且可以将需要传递的数据作为该事件的参数。
  2. 然后,在父组件中,我们需要监听子组件触发的这个自定义事件,并在事件处理函数中接收传递过来的数据。
  <body>
    <div id="app">
        <counter :count="count" @increase="parentIncrease"></counter>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                count:5
            },
            methods: {
                parentIncrease:  function(num){
                    this.count += num;
                }
            },
            components: {
                'counter': {
                    props: {count:Number},
                    methods:{
                        childIncrease: function(num){
                            this.$emit('increase', num);
                        }
                    },
                    template: `<button @click="childIncrease(5)">{{count}}</button>`
                }
            }
        })
    </script>
</body>

总的来说,子组件通过触发一个自定义事件并将数据作为参数来传递数据给父组件,父组件通过监听这个自定义事件并在事件处理函数中接收数据来获取子组件传递过来的数据。

6.兄弟组件的数据传递 #

EventBus,也就是事件总线,是实现Vue组件间通信的一种方式。它本质上是一个空的Vue实例,通过利用Vue自身的$emit和$on方法实现事件和数据的传递。对于实现兄弟组件间的通信,EventBus是一种有效的手段。

以下是使用 EventBus 在 Vue2 中实现兄弟组件通信的详细步骤:

1. 创建一个 EventBus 实例

首先,我们需要创建一个全局可用的 EventBus 实例。它通常会被放置在一个单独的文件中,以便可以在任何地方导入并使用。

2. 在一个组件中发送事件

假设我们有一个名为ComponentA的组件,我们希望在其中发送一个事件,这个事件包含一条消息。我们可以使用 EventBus 的 $emit 方法来发送一个事件,并附带需要传递的数据。

在这个例子中,我们发送了一个名为 'message' 的事件,并传递了一条消息 'Hello from Component A!'。

3. 在另一个组件中监听事件

接下来,我们希望在另一个名为ComponentB的组件中接收这个消息。我们可以在这个组件的 created 钩子中使用 EventBus 的 $on 方法来监听我们之前发送的 'message' 事件。

当ComponentA发送 'message' 事件时,ComponentB 就会接收到这个事件并打印出附带的数据。

注意: 在使用 EventBus 时,需要注意清理事件监听,以防止产生不必要的内存泄露。在组件销毁时(比如在 beforeDestroy 或 destroyed 钩子中)使用 EventBus 的 $off 方法可以取消事件监听。

  <body>
    <div id="app">
      <component-a></component-a>
      <component-b></component-b>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      const EventBus = new Vue();
      var vm = new Vue({
        el: "#app",
        components: {
          "component-a": {
            created() {
              EventBus.$on("message", this.handleMessage);
            },
            beforeDestroy() {
              EventBus.$off("message", this.handleMessage);
            },
            methods: {
              sendMessage() {
                EventBus.$emit("message", "Hello from Component a!");
              },
              handleMessage(data) {
                console.log(data);
              },
            },
            template: `<button @click="sendMessage()">a sendMessage</button>`,
          },
          "component-b": {
            created() {
              EventBus.$on("message", this.handleMessage);
            },
            beforeDestroy() {
              EventBus.$off("message", this.handleMessage);
            },
            methods: {
              sendMessage() {
                EventBus.$emit("message", "Hello from Component b!");
              },
              handleMessage(data) {
                console.log(data);
              },
            },
            template: `<button @click="sendMessage()">b sendMessage</button>`,
          },
        },
      });
    </script>
  </body>

这就是如何在 Vue2 中使用 EventBus 实现兄弟组件间的通信。这种方式的一个优点是它可以实现任何两个组件间的通信,不仅仅是兄弟组件,而且这两个组件不需要有任何直接的关系。

7.Vue实例传递数据 #

在 Vue2 中,$root$parent$children$refs 是一些常用的实例属性,它们可以帮助你在组件树中导航和访问其他组件。以下是它们的详细解释:

  1. $root: 这个属性提供了对根 Vue 实例的引用。如果你在组件内部需要访问在根实例上定义的属性或方法,你可以使用 $root。但是,通常我们应该避免在子组件中直接访问根实例,因为这破坏了组件的封装性。

  2. $parent: 这个属性提供了对父组件的引用。你可以使用 $parent 访问父组件的数据和方法。然而,过度使用 $parent 可能会使得组件之间的耦合度过高,不利于代码的维护和复用。

  3. $children: 这个属性包含了当前组件的直接子组件。这是一个数组,你可以通过它访问子组件的数据和方法。但是,这个数组并不保证有任何特定的顺序,所以依赖 $children 来获取特定的子组件可能是不可靠的。

  4. $refs: 如果你需要在 JavaScript 中直接访问一个子组件,你可以在那个子组件上使用 ref 特性来设置一个标识符,然后在父组件中通过 $refs 访问到它。注意,$refs 只会在组件渲染完成后被填充,并且它是非响应式的,它不应该被用在模板中。

总的来说,虽然这些属性提供了在组件树中导航的能力,但是过度使用它们可能会导致组件之间的耦合度过高,不利于代码的维护和复用。在大多数情况下,我们应该优先使用 props 和 events 来进行父子组件之间的通信。

  <body>
    <div id="app"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      var vm = new Vue({
        el: "#app",
        data: () => ({ msg: "parent" }),
        template: `<div>parent<child></child></div>`,
        components: {
          child: {
            data: () => ({ msg: "child" }),
            template:
              '<div @click="print">child<grandchild ref="grandchild"></grandchild></div>',
            methods: {
              print() {
                console.log(this.$parent.msg);
                console.log(this.$root.msg);
                console.log(this.$children[0].msg);
                console.log(this.$refs.grandchild.msg);
              },
            },
            components: {
              grandchild: {
                data: () => ({ msg: "grandchild" }),
                template: "<div >grandchild</div>",
              },
            },
          },
        },
      });
    </script>
  </body>

8.provide #

在Vue.js中,provideinject构成了一个依赖注入的API,用于在父组件和后代组件间进行数据传递,无论后代组件层级有多深。它主要用于开发高阶插件/组件库。以下是关于provideinject的详细解释:

  <body>
    <div id="app"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      const EventBus = new Vue();
      var vm = new Vue({
        el: "#app",
        provide: {
          msg: "parentProvideValue",
        },
        template: `<div>parent<child></child></div>`,
        components: {
          child: {
            template: "<div>child<grandchild></grandchild></div>",
            components: {
              grandchild: {
                inject: ["msg"],
                template: "<div>{{msg}}</div>",
              },
            },
          },
        },
      });
    </script>
  </body>

值得注意的是,provideinject绑定并不是可响应的。也就是说,如果你更改了provide提供的对象的属性,那么子组件并不能感知到这个变化。但是,如果提供的值是一个可观察的(observable)数据源,如 Vue 实例或响应式对象,那么其变化能在子组件中被感知和响应。

另外,虽然provide/inject在某些情况下可以简化组件间的数据传递,但它并不适合用在应用级别的状态管理。在大型应用中,推荐使用Vuex来进行状态管理。

9.slot #

在 Vue.js 中,slot 是一种非常重要的内容分发 API,它允许你在组件中嵌入任意的内容。这种方式非常灵活,允许你创建可复用的组件,这些组件的一部分内容可以由使用这些组件的地方来确定。

9.1 默认插槽 #

  1. 默认插槽 (Default Slot): 如果一个组件的模板中包含一个没有 name 属性的 <slot> 标签,那么这就是一个默认插槽。在使用该组件的地方,如果你在组件标签内部添加一些内容,那么这些内容会被插入到组件模板的默认插槽位置。
  <body>
    <div id="app"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      var vm = new Vue({
        el: "#app",
        template: `<div><default-slot>default</default-slot></div>`,
        components: {
          "default-slot": {
            template: "<div><slot></slot></div>",
          },
        },
      });
    </script>
  </body>

9.2 具名插槽 #

  1. 具名插槽 (Named Slot): 如果你的组件模板中需要有多个插槽,那么你可以使用具名插槽。具名插槽是通过给 <slot> 标签添加 name 属性来定义的。
  <body>
    <div id="app"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      var vm = new Vue({
        el: "#app",
        template: `
        <named-slot>
            <template v-slot:header>
                <h1>header</h1>
            </template>
            <h1>default</h1>
            <template v-slot:footer>
                <h1>footer</h1>
            </template>
        </named-slot>
        `,
        components: {
          "named-slot": {
            template: `
            <div>
              <header>
                <slot name="header"></slot>
              </header>
              <main>
                <slot></slot>
              </main>
              <footer>
                <slot name="footer"></slot>
              </footer>
            </div>
            `,
          },
        },
      });
    </script>
  </body>

在这个例子中,<h1>header</h1> 会被插入到 named-slot 的 "header" 插槽,<h1>footer</h1> 会被插入到 "footer" 插槽,其余内容会被插入到默认插槽。

9.3 作用域插槽 #

作用域插槽是 Vue.js 的一个强大特性,允许父组件接入子组件中的一部分自定义区域,并在这些区域中可以访问子组件传递出来的数据。在 Vue 2.x 中,你可以通过在 <slot> 标签上使用 v-bind 来定义要传递到父组件的数据。

  1. 定义作用域插槽: 子组件可以在 <slot> 标签上使用 v-bind 来定义要传递到父组件的数据
  2. 使用作用域插槽: 父组件可以在模板中使用特殊的 v-slot 指令来访问子组件传递过来的数据。
  <body>
    <div id="app"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      var vm = new Vue({
        el: "#app",
        template: `
        <scoped-slot>
          <template v-slot:header="slotProps">
            <h1>{{ slotProps.user.name }}</h1>
          </template>
        </scoped-slot>
        `,
        components: {
          "scoped-slot": {
            data:()=>({user:{name:'zhufeng'}}),
            template: `
            <div>
              <slot name="header" :user="user"></slot>
            </div>
            `
          }
        }
      });
    </script>
  </body>

10.component #

在 Vue.js 中,<component> 是一个保留的标签,用于动态组件。你可以使用 is 特性来决定要渲染哪个组件。

这是 <component> 标签和 is 属性的主要用法:

<component :is="componentName"></component>

其中,componentName 是一个字符串,用于指定要渲染的组件。你也可以使用对象语法,传入一个组件的选项对象。

  <body>
    <div id="app"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      var vm = new Vue({
        el: "#app",
        data: { currentComponent: "component-a" },
        template: `<component :is="currentComponent"></component>`,
        components: {
          "component-a": {
            template: `<div>A</div>`,
          },
          "component-b": {
            template: `<div>B</div>`,
          },
        },
      });
      //vm.currentComponent='component-b'
    </script>
  </body>

11.虚拟DOM #

虚拟DOM (Virtual DOM) 是一个核心概念,存在于许多前端框架中,比如 Vue 和 React。这是一个在浏览器的 JavaScript 引擎中生成的轻量级 JavaScript 对象树,用来描述实际DOM。

在 Vue 2 中,虚拟 DOM 的主要作用如下:

  1. 性能优化:每当数据发生变化时,Vue 将创建一个新的虚拟 DOM 结构,然后与旧的虚拟 DOM 结构进行对比。这种比较是在 JavaScript 层面上进行的,比直接操作真实 DOM 快得多。Vue 能够识别出哪些元素发生了变化,然后只更新那些变化的元素,而不是整个DOM树。

  2. 跨平台性:虚拟DOM不仅可以在浏览器环境下工作,还可以用于服务器端渲染(SSR),甚至在原生移动应用中使用。

  3. 解耦视图与实际的 DOM 操作:使用虚拟 DOM,开发者可以专注于状态管理,不需要关注 DOM 操作的细节。虚拟 DOM 的使用使得组件的重用和测试更为简单。

基本上,Vue 2 的虚拟 DOM 使用的是一种“diffing”算法。首先,当一个组件的状态发生改变时,Vue 将创建一个新的虚拟 DOM 并与旧的虚拟 DOM 进行比较。然后,Vue 将计算出最小的必要修改,然后将这些修改应用到实际的 DOM 上。这样,应用程序的状态始终与界面保持同步,同时也尽量减少了DOM操作,从而提高性能。

12.render #

Vue.js 提供了一个名为 render 的选项,允许用户通过 JavaScript 直接编写渲染函数,返回虚拟 DOM 来描述你的组件该如何被渲染。这是一个相对底层的操作,但是能够提供极大的灵活性。这个功能大部分用于库和高级组件的开发。

在 Vue 组件中,render 函数有更高的优先级,也就是说,如果一个 Vue 组件同时设置了 templaterender 选项,那么 Vue.js 将会忽略 template,只使用 render 函数。

一个最简单的 render 函数的例子如下:

<body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            render: function (createElement) {
                return createElement('div',{style:{color:'red'}}, 'hello')
            }
        })
    </script>
</body>

在这个例子中,render 函数接收一个函数作为参数,我们通常命名为 createElement(或者简称 h)。这个函数用来创建虚拟 DOM。

渲染函数的主要目的是返回一个虚拟 DOM,这可以是通过 createElementh)函数创建的一个虚拟节点(VNode),也可以是其他渲染函数返回的结果。

createElement 接收三个参数:tagdatachildrentag 是标签名或组件,data 是一个包含模板相关属性的对象,children 是一个包含了子虚拟 DOM 节点的数组。children 可以是更多的 createElement 调用,或者如果是文本的话,可以直接是字符串。

总的来说,render 函数就是提供了一种更接近编程的方式来声明组件的输出,相比模板语法,它更加灵活,能够使用完整的 JavaScript 语言特性,更适合创建复杂、逻辑密集的视图。

13.functional #

在 Vue.js 中,functional 代表功能性的,所谓功能性组件就是它们是无状态的,也就是说他们不包含响应式数据,也不有任何的实例方法,没有 this 上下文。

functional: true 表示这是一个功能性组件。这种类型的组件被认为是纯粹的展示型组件,不包含任何状态,也不会触发任何的生命周期钩子函数。因此,它们比常规 Vue 组件更轻量,渲染性能也更高。

在 Vue 2.x 中,功能性组件需要在其定义时显式指定 functional: true,并且不使用 data 选项,取而代之的是,它们在渲染函数中接收一个上下文对象。这个上下文对象提供了一些对父组件数据的引用,如 propsslotsscopedSlotsdataparent,以及 listeners

一个典型的功能性组件的定义形式如下:

<body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
    <script>
        Vue.component('my-component', {
            functional: true,
            props: {id:String},
            render: function (createElement, context) {
                const props={
                    id:context.props.id,
                    style:context.data.style
                }
                return createElement('div', props, context.children)
            }
        })
        new Vue({
            el: '#app',
            template:`<my-component  id="container" :style="{color:'red'}">hello</my-component>`
        })
    </script>
</body>

在这个例子中,render 函数接收一个 context 对象,这个对象包含了一些有用的信息,如:propschildrenslots,等等。

在 Vue.js 中,render 函数是用来生成虚拟DOM节点的。它是一个可选的函数,如果你不使用模板,那么可以使用 render 函数来描述组件如何渲染。render 函数通常看起来像这样:

render: function (createElement) {
  // return virtual DOM
}

在功能组件的 render 函数中,context 对象包含以下属性:

在 Vue.js 的 FunctionalRenderContext 中,propsdata 各有不同的含义和作用。

props

props 是从父组件传递给函数式组件的数据。因为函数式组件没有自己的状态,它们无法像状态组件那样使用 data。因此,他们依赖于通过 props 接收数据。这些数据是只读的,也就是说,你不能在函数式组件中改变 props 的值。

data

FunctionalRenderContext 的上下文中,data 是一个对象,包含了从父组件传递给函数式组件的数据,以及关于组件本身的一些信息。data 对象中的属性可能包括:attrs(普通HTML属性)、props(已声明的props)、domProps(DOM属性如 innerHTML)、on(事件监听器)、nativeOn(原生事件监听器)、classstylekeyref等等。

在 Vue 中,style 和 class 是特殊的属性,它们不会被包含在 props 中,而是会被包含在 context.data 对象中。

14 Vue.extend #

Vue.extend 是一个类级别的方法,用于生成一个 Vue 的子类,允许创建可复用的组件构造器。你可以传入一个包含 Vue 组件选项的对象作为参数。

下面是一个简单的例子:

<body>
    <div id="app1"></div>
    <div id="app2"></div>
    <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
    <script>
        var ExtendComponent = Vue.extend({
            template: '<div>hello</div>'
        })
        new ExtendComponent().$mount('#app1')
        new ExtendComponent().$mount('#app2')
    </script>
</body>

在这个例子中,我们使用 Vue.extend 创建了一个新的 Vue 子类,然后实例化并挂载到 DOM 中。我们将一个包含 template 选项的对象传递给 Vue.extend,这个选项和你在新建一个 Vue 实例时使用的选项是一样的,如 datamethodscomputedwatchlifecycle hooks 等。

生成的组件构造器可以作为自定义元素或者在 route 配置中使用。因此,Vue.extend 主要用于 Vue 的全局扩展,生成的构造器通常会与 new Vue() 结合使用生成新的 Vue 实例。

总结一下,Vue.extend 的主要用途是创建可复用的组件构造器,你只需要传入一个包含组件选项的对象即可,非常方便。

15. Vue.use #

Vue.use是一个全局方法,它用于安装 Vue 插件。如果插件是一个对象,它必须提供 install 方法;如果插件是一个函数,它会被作为 install 方法。install 方法将被作为 Vue 的构造函数以及可能存在的可选选项传入。

以下是一个非常基础的 Vue.use 实现:

function Vue() {}

Vue._installedPlugins = [];  // 用于存储已经安装的插件

Vue.use = function (plugin, ...options) {
    // 检查插件是否已经安装
    if (Vue._installedPlugins.includes(plugin)) {
        return Vue;
    }

    // 如果插件是一个对象,那么需要提供 install 方法
    // 如果插件是一个函数,那么直接作为 install 方法
    let install = plugin.install || plugin;

    // 调用 install 方法,将 Vue 的构造函数以及可能存在的可选选项传入
    if (typeof install === 'function') {
        install(Vue, ...options);
    }

    // 将插件添加到已安装插件列表
    Vue._installedPlugins.push(plugin);

    return Vue;
};

// 使用示例
let myPlugin = {
    install: function(Vue, options) {
        Vue.prototype.$myMethod = function() {
            console.log('This is a plugin method.');
        }
    }
}

Vue.use(myPlugin);

let vm = new Vue();
vm.$myMethod();  // 打印 'This is a plugin method.'

这个简单实现会检查插件是否已经安装,如果没有安装,就会调用插件的 install 方法,并将插件添加到已安装插件列表。然后 install 方法会接收 Vue 的构造函数以及可能存在的可选选项。

同样,请注意,这只是一个基本的实现,并不包括 Vue 完整的插件系统功能。在 Vue 的实际实现中,插件可以包含多种复杂的功能,这个简单实现并没有处理这些复杂情况。

16. Vue.mixin #

在 Vue.js 中,Vue.mixin 是一个全局方法,它用于在全局范围内应用一个混入。这意味着当你使用 Vue.mixin 时,你在混入对象中定义的任何选项都将被混入到每一个创建的 Vue 实例(包括根实例和所有组件)中。

这是一个例子:

<body>
    <div id="app1"></div>
    <div id="app2"></div>
    <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
    <script>
        Vue.mixin({
            created: function () {
                var store = this.$options.store
                if (store) {
                    this.$store = store;
                }
            }
        })
        let store = {state:{number:0}};
        var vm = new Vue({
            store
        })
        console.log(vm.$store);
    </script>
</body>

在这个例子中,我们定义了一个混入,该混入添加了一个 created 钩子函数。该函数在每个 Vue 实例(包括根实例和所有子组件)被创建时执行。因此,当我们创建一个新的 Vue 实例,并提供了一个 myOption 选项时,该选项将被打印出来。

需要注意的是,Vue.mixin 是全局的,它影响了所有创建的 Vue 实例。因此,你应该慎重使用它,以避免造成潜在的冲突。在大多数情况下,你可能更倾向于使用组件的 mixins 选项,它允许你只在特定组件中应用混入。

在混入中的选项会与组件的选项进行"合并"。大多数情况下,如果组件和混入对象含有同名选项,那么这些选项将以恰当的方式进行合并。比如,数据对象在内部会进行递归合并,最终生成一个新的数据对象。而同名的钩子函数将合并为一个数组,因此都将被调用。详情可以参考 Vue.js 文档中的 选项合并规则

Vue.mixin = function (mixin) {
    this.prototype.options = Object.assign({}, this.prototype.options, mixin);
}