Vue2.x 源码 -组件的注册

上一篇:Vue2.x 源码 - 生命周期

在 Vue 中,用户在自定义组件之前都必须先注册组件,Vue 提供了两种注册方式:全局注册、局部注册;这两种注册组件的方式结果是不同的,全局注册的组件可以在整个应用中直接使用,局部注册的组件只能在当前组件中使用;接下来就看看这两种注册方式。

全局注册

要注册⼀个全局组件,可以使⽤ Vue.component(tagName, options) ,例如:

Vue.component('name', {
//选项
})

那么, Vue.component 函数是在什么时候定义的呢,它的定义过程发⽣在最开始初始化 Vue 的全局函数的时候,代码在 src/core/global-api/assets.js 中:
1、初始化

Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

2、API 实现

export const ASSET_TYPES = ['component', 'directive','filter']
export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string, //名称
      definition: Function | Object //方法
    ): Function | Object | void {
      // 未传入 definition,则表示是获取,直接return
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        // 注册组件,检查组件名是否规范
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          //在definition中没有传name,id就是组件的name
          definition.name = definition.name || id
          //把definition传入extend,返回Vue的子构造器,所以component的第二个参数可以是Vue.extend函数
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          //默认 bind和 update都是入参函数
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

函数⾸先遍历 ASSET_TYPES ,如果没有传入方法表示获取,传入了则表示是创建;然后对名称合法性进行校验,是 component 的话调用 this.options._base.extend(definition)global-api.js 页面有这样一串代码 Vue.options._base = Vue 所以这里相当于是 Vue.extend , 这串代码相当于 Vue.extend 把这个对象转换成⼀个继承 于 Vue 的构造函数,最后通过 this.options[type + 's'][id] = definition 把它挂载到 Vue.options.components 上:

import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'
// 注册前
const options = {
  components: {}
}
// 注册
Vue.component('HelloWorld', HelloWorld)
// 注册后
const options = {
  components: {
    HelloWorld: function VueComponent () { ... }
  }
}

在 extend 继承的时候会有这样一段代码:

Sub.options = mergeOptions(
   Super.options,
   extendOptions
 )

也就是说它会把 Vue.options 合并到 Sub.options,也就是组件的 optinons 上;然后在组件的实例化阶段,会执⾏ merge options 逻辑,把 Sub.options.components 合并到 vm.$options.components 上;

所以全局注册组件 都在Vue.options.components选项上,这就是全局注册的组件可以在任意地方使用的根本原因了;

在合并参数的时候有下面这段代码:

function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

将全局注册组件合并到子组件的 components 选项的原型上;

然后在创建 vnode 的过程中,会执行 _createElement 方法,在 src/core/vdom/create-element.js 中,涉及到组件的时候有这样一个判断 isDef(Ctor = resolveAsset(context.$options, 'components', tag);先看⼀下 resolveAsset 的定义,在 src/core/utils/options.js 中:

export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}

通过多种方法来获取组件的 id,也可以说是组件名;目的是拿到这个组件的构造函数并作为 createComponent 方法的参数来生成组件的 vnode;

全局组件注册渲染流程

1、创建 components :在 globalApi 中进行初始化,赋值 Object.create(null)
2、globalApi 的 initAssetRegisters 方方法中extend 继承 :创建了一个基于父Vue的子类,继承父类的方法;
3、extend 中 mergeOptions 参数合并 :并合并父类的属性;
4、在挂载过程中先后调用:mountComponent 、render、_update,生成 updateComponent 方法,然后用 updateComponent 创建 Watcher;
5、在 render() 过程中执行 createElement,然后执行 createComponent 来生成组件的 vnode;
6、createComponent 过程中回执行 installComponentHooks 来出来组件的钩子函数;
7、获取到的 render 函数更新渲染;

局部注册
import msg from './components/msg'
export default {
	components:{ msg }
}

局部注册组件会在 Vue 实例初始化阶段在合并选项的时候把 components 合并搭配 vm.$options.components 上,这样在resolveAsset 的时候就可以拿到组件的构造函数并最为参数传递给 createComponent 方法来生成组件的 vnode;

局部组件比较简单,就是在初始化的时候作为参数合并到 vm.$options.components

createComponent 的流程可以参考:Vue2.x 源码 - render 函数生成 VNode

组件化和模块化区别

1、组件化:从 UI 界面上来划分,包含这个组件的 HTML、CSS、JS,可复用;
2、模块化:从代码逻辑层面来划分,包含一些复用的 JS、CSS 等;

下一篇:Vue2.x 源码 - 异步组件

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值