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

发布于:2023-02-03 ⋅ 阅读:(522) ⋅ 点赞:(0)

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

vue.extend

如何使用

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

// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))

// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })

// 获取注册的组件 (始终返回构造器)
var MyComponent = Vue.component('my-component')

过程分析

在前面 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
      }
    }
  })
}

如注释所述,对于注册组件的流程,先确定用户传入的 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
  }
}

如以上代码注释所述,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
  },
  // ...
}

或者直接

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于 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 中存储。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到