【源码解析】vue.component是如何实现注册全局组件的,其实就是把组件挂在Vue.options.components上

1,516 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

前言

大家好,上一篇文章mixin的原理中我们对vue2中的mixin进行了源码解读,通过学习我们掌握了mixin是如何运行的以及它又是如何实现对象合并的。今天我们继续来分享Vue中另一个全局API - component。相信用过vue2的小伙伴对这个api并不陌生,相对于前几个全局API来说,component应该算是比较常用的了。关于该API的作用如果用一句话来概括的话就是:

用于注册或获取全局组件

下面我们先来看一下它的用法,然后再去解析一下它的源码。

案例展示

在日常vue开发中,每个.vue文件其实就是一个组件,如果我们想在某个组件a中使用另一个组件b,那么必须在a中通过import的先将b组件导入并在components中注册,然后才能在a中使用b。这种方式使用的是局部组件注册的实现的,也就是说哪里用到就在哪里注册。然而还有一种场景就是:某个组件是一个通用组件,可能需要在每个组件中都会用到,那么如果还使用局部注册每次都导入的话就太麻烦了。因此这个时候我们就可以利用Vue.component进行全局注册,一次注册终身受益。

我们还是以上篇文章中的a/b/c组件为例,现由于业务需要,要让每个组件中都显示一下项目的标题,那么这个时候我们就可以通过vue.component注册一个全局组件header,然后再在每个组件中直接使用即可实现了。

  • main.js
Vue.component('myheader',{
    template:'<h1>西瓜watermelon</h1>'
})
// 或者新建一个myheader.vue,把<h1>西瓜watermelon</h1>放在myheader.vue中
// 然后通过import导入myheader.vue,并用如下的方式注册
// Vue.component('myheader',myheader)
  • a.vue/b.vue/c.vue
<div>
    <myheader />
    <div>{{msg}}</div>
</div>

caolouyaqian (1).gif

源码解析

通过上面比较啰嗦的赘述终于要进入我们的正题了。下面我们就一起来看下源码吧(src/core/global-api/assets.js)

const ASSET_TYPES = ['component', 'directive', 'filter']

ASSET_TYPES.forEach(type => { 
  Vue[type] = function (
    id: string,
    definition: Function | Object
  ): Function | Object | void {
    if (!definition) {
      return this.options[type + 's'][id]
    } else {
      if (type === 'component' && isPlainObject(definition)) {       
        definition.name = definition.name || id     
        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
    }
  }
})

以上就是Vue.component的源码(这样说有一点瑕疵),确切的说应该是Vue.component、Vue.directive和Vue.filter的源码,因为这三个方法的方式都差不多并且也不是很复杂,所以就放在一起做了。我们还是以component为例来解读一下:

  • 首先是导入src/shared/constants.js,这里定义了一个ASSET_TYPES数组,数组的内容就是['component', 'directive', 'filter']
  • 然后遍历该数组依次给Vue添加三个对应的静态方法component、directive和filter
  • 该方法接收两个参数:string类型id和函数或对象类型的definition
  • 在该函数体内首先判断definition是否存在,如果不存在则认为是获取而不是注册组件,则直接将组件返回
    • 这里的this就是Vue本身,在Vue上有个options属性,而像components、directives等这些都会挂在Vue的options上,而所有的组件又都挂在了components上,因此通过this.options.components[id]就可以获得对应的组件
  • 如果definition存在(else分支),则进行下一步判断,也就是对component和directive进行单独处理
  • 如果type值是一个component并且definition是一个纯对象
    • 则看definition是否有name属性,如果有直接使用,如果没有则用传进来的id作为definition的name
    • 然后通过Vue.extend函数对definition进行扩展并将返回值重新赋值给definition(在前面我们已经分享过关于extend这个api了,其实就是通过extend扩展了一个Vue的子类。那么在这里也就是说将definition通过extend变成Vue的子类然后重新赋值给definition)
    • 在Vue的options上还有个_base属性,而这个_base其实指向的也是Vue,这里没明白为啥不直接用this.extend而是要通过_base绕一下
  • 最后再通过this.options[type + 's'][id] = definition将definition挂到Vue的options的components上,翻译过来就是:Vue.options.components[id] = definition 这样就完成全局组件的注册了

总结

如果将Vue通过console.dir(Vue)打印出来,你会发现:

  • 所有的全局组件都是挂在这个Vue.options.components上的,其实我们也可以直接用Vue.options.components[组件名] = 组件的形式也能进行全局注册
  • 在调用Vue.component时,如果第二个参数传递的是一个纯对象,那么在源码中会通过Vue.extend将对象扩展成Vue的子类然后再挂载到Vue的options的components上,那也就是说我们在使用Vue.component时第二个参数可以直接传递一个Vue.extend也能实现同样的效果

caoshenhuinan.gif