• Vue2源码学习笔记 - 17.注册组件


    组件是我们用 Vue 做开发时经常用的功能模块,虽然多数时候我们是写的单文件组件,有时候也说到局部组件和全局组件,其实它们的底层都是用的 Vue.component 函数进行的组件注册,这一节我们来详细研究学习 Vue 的组件注册。

    vue.extend

    如何使用

    我们先简单看下注册组件的例子:

    // 注册组件,传入一个扩展过的构造器
    Vue.component('my-component', Vue.extend({ /* ... */ }))
    
    // 注册组件,传入一个选项对象 (自动调用 Vue.extend)
    Vue.component('my-component', { /* ... */ })
    
    // 获取注册的组件 (始终返回构造器)
    var MyComponent = Vue.component('my-component')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    过程分析

    在前面 Vue 类的定义 章节我们研究了 Vue 的定义流程,其中有个 initGlobalAPI 函数,它里面又调用了 initAssetRegisters 函数定义 Vue.component 静态方法,我们看代码:

    file: /src/core/global-api/assets.js

    export function initAssetRegisters (Vue: GlobalAPI) {
      ASSET_TYPES.forEach(type => {
        Vue[type] = function (
          id: string,
          definition: Function | Object
        ): Function | Object | void {
          if (!definition) {
            return this.options[type + 's'][id]
          } else { // 定义 Vue.component 注册组件 的静态方法
            if (type === 'component' && isPlainObject(definition)) {
              // 确保有 name 属性
              definition.name = definition.name || id
              // this.options._base === Vue
              definition = this.options._base.extend(definition)
            }
            if (type === 'directive' && typeof definition === 'function') {
              definition = { bind: definition, update: definition }
            }
            this.options[type + 's'][id] = definition
            return definition
          }
        }
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    如注释所述,对于注册组件的流程,先确定用户传入的 definition.name 属性,没有值则赋值 id,然后调用 this.options._base.extend,因为是静态方法,所以它等价于调用 Vue.extend(definition),并把该返回值放入 Vue.options[‘components’] 对象中。

    Vue.extend

    下面我们来看看 Vue.extend,它的作用是使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。它在函数 initExtend 中定义:

    file: /src/core/global-api/extend.js

    export function initExtend (Vue: GlobalAPI) {
      Vue.cid = 0
      let cid = 1
      
      // Class inheritance
      Vue.extend = function (extendOptions: Object): Function {
        extendOptions = extendOptions || {}
        const Super = this
        const SuperId = Super.cid
        // 从缓存中获取组件构造函数(如果存在的话)
        const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
        if (cachedCtors[SuperId]) {
          return cachedCtors[SuperId]
        }
        const name = extendOptions.name || Super.options.name
        
        // 定义子类构造函数,同 Vue
        const Sub = function VueComponent (options) {
          this._init(options)
        }
        
        // 原型继承自 Vue
        Sub.prototype = Object.create(Super.prototype)
        Sub.prototype.constructor = Sub
        Sub.cid = cid++
        
        // 合并 options 选项
        Sub.options = mergeOptions(
          Super.options,
          extendOptions
        )
        Sub['super'] = Super
        // 初始化处理 props 属性,代理到 vm 上
        if (Sub.options.props) {
          initProps(Sub)
        }
        // 初始化处理 computed 计算属性,响应式化并代理到 vm 上
        if (Sub.options.computed) {
          initComputed(Sub)
        }
        // 启用 extend\mixin\use 等方法
        Sub.extend = Super.extend
        Sub.mixin = Super.mixin
        Sub.use = Super.use
        
        // Vue.component\Vue.directive\Vue.filter 赋给子类
        ASSET_TYPES.forEach(function (type) {
          Sub[type] = Super[type]
        })
        
        // 构造类放入自身 options.components 中
        if (name) { Sub.options.components[name] = Sub }
        
        // 缓存多种 options,后续实例化类时需用以检查更新
        Sub.superOptions = Super.options
        Sub.extendOptions = extendOptions
        Sub.sealedOptions = extend({}, Sub.options)
    
        // 缓存子类构造函数,在局部注册组件时防止重复创建构造函数
        cachedCtors[SuperId] = Sub
        return Sub
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    如以上代码注释所述,Vue.extend 函数根据传入的扩展选项创建一个 Sub 构造函数,原型继承于 Vue,然后合并扩展选项和 Vue.options 为 Sub.options,再处理 props 和 computed 属性,扩展静态方法等。

    最后把 Vue.options、扩展选项等引用保存在 Sub 静态属性上,在后续实例化组件时用来检查更新选项的操作。在函数的最后返回 Sub 构造函数,在 Vue.component 中把它存入 Vue.options.components 对象中以完成全局注册。

    局部注册

    局部注册我们在模块化开发中经常用到,比如在 Babel 和 webpack 使用 ES2015 模块来定义组件,然后像下面这样注册:

    import ComponentA from './ComponentA.vue'
    
    export default {
      components: {
        ComponentA
      },
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    或者直接

    var ComponentA = { /* ... */ }
    var ComponentB = { /* ... */ }
    new Vue({
      el: '#app',
      components: {
        'component-a': ComponentA,
        'component-b': ComponentB
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。局部注册的组件的构造函数是在创建其对应的 Vnode 对象时在函数 createComponent 中才创建的。

    在 createComponent 中也是通过调用 Vue.extend 创建的局部组件的构造函数,如果创建组件的 Vnode 多次的话,那么构造函数也会重复创建吗?答案是否定的,在 Vue.extend 中创建子类之后会把子类构造函数存入 Vue.extend 的实参 extendOptions._Ctor 中,即是 cachedCtors 中,它会在第二次 extend 时直接返回(见上面 Vue.extend 源码及注释)。

    总结:

    不管是全局注册的 Vue.component 方法还是局部注册的 options.components 选项属性,它们底层都是调用的 Vue.extend 去继承父类(通常是 Vue)创建子类构造函数,只是 extend 的时机不同。全局注册的组件会在 Vue.options.components 中存储,局部的会在 Vue 的对象实例 vm.$options.components 中存储。

  • 相关阅读:
    那些测试行业的细分岗位,你知道多少?薪资又如何?
    Kubernetes(k8s)资源控制器 RS、Deployment详细介绍
    新特性解读 | MySQL 8.0 GIPK 不可见主键
    【git的使用方法】——上传文件到gitlab仓库
    编程英语生词笔记本
    jvm-直接内存笔记【详细】
    团队规范之前后端协作规范
    利用宏定义在编译阶段检查结构体大小的方法
    1.3数据结构之复杂度 力扣题目移除元素
    【GDB】使用 GDB 自动画红黑树
  • 原文地址:https://blog.csdn.net/dclnet/article/details/126116246