【手写 Vue2.x 源码】第三十五篇 - 组件部分 - Vue.component 实现

192 阅读4分钟

一,前言

上篇,介绍了 Vue 组件与初始化流程,涉及以下几部分:

  • 组件使用介绍:全局组件、局部组件的定义和优先级;
  • 组件初始化流程介绍:Vue.component、Vue.extend、保存组件构造函数、组件合并、初渲染和更新;

本篇,主要介绍 Vue.component 的实现;


二,Vue.component 实现

1,Vue.component 如何加载

Vue.component 是一个全局 API;

Vue 初始化流程中,通过 initGlobalAPI 方法,对全局 API 进行初始化操作;

// src/index.js

/**
 * 在 vue 中所有的功能都通过原型扩展(原型模式)的方式来添加
 * @param {*} options vue 实例化传入的配置对象
 */
function Vue(options) {
    this._init(options);
}

initMixin(Vue)
renderMixin(Vue)
lifeCycleMixin(Vue)
initGlobalAPI(Vue) // 初始化 global Api

export default Vue;

initGlobalAPI 方法中,处理 Vue Global API

// src/global-api/index.js

export function initGlobalAPI(Vue) {

  // 全局属性:Vue.options
  // 功能:用于存放属性 mixin, component, filter, directive
  Vue.options = {}; 
  
  Vue.mixin = function (options) {
    this.options = mergeOptions(this.options, options);
    return this;  // 返回 this,提供链式调用
  }
  
  /**
   * Vue.component API
   * @param {*} id          组件名
   * @param {*} definition  组件定义
   */
  Vue.component = function (id, definition) {
    // todo ...
  }
}

2,Vue.component 如何定义

// Vue.component 全局 API 的定义
Vue.component = function (id, definition) {}
  
// Vue.component 全局 API 的使用
Vue.component('my-button', {
  name:'my-button',
  template:'<button>全局组件</button>'
})

2.1 组件名 name

每个组件都拥有一个唯一的组件名称,即组件的唯一标识;

  • 组件名称默认使用 Vue.component 的第一个参数 id
  • definition 中包含 name 属性,则使用 name 属性值作为组件名;

代码实现:

/**
  * Vue.component
  *
  * @param {*} id          组件名
  * @param {*} definition  组件定义
  */
Vue.component = function (id, definition) {
  // 获取组件名 name:优先使用 definition.name,默认使用 id
  definition.name = definition.name || id;
}

2.2 组件定义 definition

Vue.component的第二个参数definition,即组件定义;

组件定义definition,即可以是函数,也可以是对象:

  • 函数:Vue.extend({ /* ... */ }
  • 对象:{ /* ... */ }
// 写法 1:注册组件,传入一个扩展过的构造器 
Vue.component('my-component', Vue.extend({ /* ... */ })) 

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

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

若入参 definition 为对象,在 Vue.component 方法内部将通过 Vue.extend 进行一次处理:

// src/global-api/index.js

/**
  * 使用基础的 Vue 构造器,创造一个子类
  * 
  * @param {*} definition
  */
Vue.extend = function (definition) {

}

/**
  * Vue.component
  *
  * @param {*} id          组件名
  * @param {*} definition  组件定义:对象或函数
  */
Vue.component = function (id, definition) {

  // 获取组件名 name:优先使用 definition.name,默认使用 id
  let name = definition.name || id;
  definition.name = name;

  // 若参入 definition 为对象,需要使用 Vue.extend 进行包裹
  if(isObject(definition)){
    definition = Vue.extend(definition)
  }
  
  // 若参入 definition 不是对象(是函数),不处理
}

Vue.extend 的作用:使用基础的 Vue 构造器,创建出一个子类;

下篇进行详细介绍:【Vue2.x 源码学习】第三十六篇 - Vue.extend 实现

3,组件构造函数的全局保存

initGlobalAPI 方法中,Vue.options 用于存放全局属性;

  • 全局属性,会在每个组件初始化时,被合并到组件上;
  • 在全局组件中,也会使用全局属性;

所以,全局组件也要注册到Vue.options上;

这就需要对 Vue.options 对象进行扩展,构造 Vue.options.components 用于存放全局组件(将全局组件注册到 Vue.options 上):

// src/global-api/index.js

export function initGlobalAPI(Vue) {

  // 全局属性:Vue.options
  // 功能:存放 mixin, component, filter, directive 属性
  Vue.options = {}; // 每个组件初始化时,将这些属性放入组件
  // 用于存放全局组件
  Vue.options.components = {};
  
  /**
   * 使用基础的 Vue 构造器,创造一个子类
   *
   * @param {*} definition 
   */
  Vue.extend = function (definition) {
    // todo ...
  }
  
  /**
   * Vue.component
   *
   * @param {*} id          组件名(默认)
   * @param {*} definition  组件定义:可能是对象或函数
   */
  Vue.component = function (id, definition) {

    // 获取组件名 name:优先使用 definition.name,默认使用 id
    let name = definition.name || id;
    definition.name = name;

    // 若入参 definition 是对象,需要使用 Vue.extend 进行包裹
    if(isObject(definition)){
      definition = Vue.extend(definition)
    }

    // 将 definition 对象保存到全局:Vue.options.components
    Vue.options.components[name] = definition;
  }
}

构造 Vue.options.components 的目的和作用:

  • 相当于在全局维护了一个组件名与组件构造函数的映射关系;
  • 便于后续通过全局上的 vm.constructor.options 进行全局、局部组件的合并;
  • 便于后续根据组件虚拟节点 tag 标签,能够快速、直接地查找到该组件的构造函数,并进行组件的实例化;

4,Vue.component 总结

  • Vue.componentVue Global API
  • 通过调用 Vue.component 进行全局组件声明;
  • Vue.component 方法内部,当第二个参数组件定义 definition 为对象时,会通过 Vue.extend 处理并生成子类,即组件的构造函数;
  • 将组件名与组件构造函数的映射关系维护到 Vue.options.components,提供后续组件合并与组件实例化使用;

三、结尾

本篇,主要介绍了 Vue 初始化流程中 Vue.component 的实现:

  • Vue.component 全局 API 的初始化处理;
  • Vue.component 的定义和参数说明;
  • 组件构造函数全局存储的方式和作用;

下篇,Vue.extend 实现;


维护日志

  • 20210809:
    • 修改拼写错误的方法名;
    • 微调文章排版;
  • 20210811:
    • 添加“组件定义 definition” 两种的代码示例;
    • 调整部分描述语句,使语义更加贴切易懂;
    • 微调部分排版顺序,使文字与代码示例之间关系层级明确清晰;
    • 添加“Vue.component 总结”部分
  • 20230223:
    • 添加内容中的代码和关键字高亮;
    • 优化部分内容描述,使表达更加清晰易懂;
    • 调整了部分代码注释和格式,使逻辑更好理解;
    • 更新文章概要;
  • 20230303:
    • 重新阅读本篇,梳理并调整了部分内容的描述,使表述和语义更加准确,更好理解;