大家平时用vue开发项目的时候,大部分的时间就是在写组件吧,而大家都已经习惯了single-file components (单文件组件),写起来已经轻车熟路,但不知道大家有没有想过一个问题:一个组件是怎么被注册的?或者说vue是怎么知道他是一个组件的?
答案其实也很简单,只要在components对象中声明就行了,但具体是怎么注册的呢,声明这步好像仅仅是给组件起了个名字,我在开发组件的时候,比如ComponentA.vue,我给他添加一个属性name:ComponentA,这样不是就能用了吗?有人会说了这个属性不是必选的,是的,如果不写,那我默认文件名不就可以了,所以声明一个组件的内部逻辑是什么呢?一个组件到底是一个纯粹的对象,还是一个函数呢?带着这些问题,我们一起探索下。
不知道大家有没有遇到过开发一个项目不需要webpack,vue-loader这些工具的情况,比方说直接引入vue:

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

我们如何注册一个组件呢?
官方的API:Vue.component

Vue.component('component-a', { /* ... */ })

new Vue({ el: '#app' })

这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
但是问题也就来了,如果项目的需求增多,随之而来会有更多的组件需要开发,就很难保证组件的名字不会冲突,这个时候就需要模块系统了,vue当然也想到了这一点,它提供了另外一种组件注册的方式——局部注册,来适应模块化的开发,局部注册的代码差不多是这样的:

var ComponentA = { /* ... */ }

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA
  }
})

引入模块系统后就可以变成下面这样:

ComponentA.js:
const  ComponentA = { /* ... */ }

app.js:
const ComponentA = require(./ComponentA.js)
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA
  }
})

这样就解决了命名空间的问题,同时我们还可以做一个假设:Vue.component方法的作用就是声明一个组件并且把它放在实例的components对象中;
但是组件的注册又必须在实例化应用的前面,貌似这个假设行不通!
不着急,我们来看下Vue内置的组件transition,我们都知道这是一个全局组件,在任何实例或者组件内部都可以直接使用,那源码中应该有这个一个声明:

Vue.component('transition',{ /* ... */ })

很可惜我们没有找到,相应的,有这么一个组件的声明:

  var Transition = {
    name: 'transition',
    props: transitionProps,
    abstract: true,
    .
    .
    .
  }

说明它是局部注册的,那它又是怎么做到能全局使用呢?继续看下源码:

  var platformComponents = {
    Transition: Transition,
    TransitionGroup: TransitionGroup
  };
 extend(Vue.options.components, platformComponents);

说明被挂在了Vue.options.components上面,那Vue.options.components又是怎么来的呢?

  function initAssetRegisters(Vue) {
    /**
     * Create asset registration methods.
     */
    ASSET_TYPES.forEach(function (type) {
      Vue[type] = function (
        id,
        definition
      ) {
        if (!definition) {
          return this.options[type + 's'][id]
        } else {
          /* istanbul ignore if */
          if (type === 'component') {
            validateComponentName(id);
          }
          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.options = Object.create(null);
    ASSET_TYPES.forEach(function (type) {
      Vue.options[type + 's'] = Object.create(null);
    });

这里写的可能有点隐晦,花点时间找一下,从上面可知:Vue.componment注册的组件被挂在了Vue.options.components上,所以Vue.options.components上的组件就是全局组件;那这样的话Vue.options.components上面的组件和实例化的时候声明在components对象上的组件就是同一个意思了,在_init方法中我们找到了如下代码:

    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    );

其中vm就是实例对象,$options就是我们传入的所有参数,类似于extend方法,把Vue.options上面的元素复制到实例中去,其中就包括components,所以我们可以得知,Vue.options.components上面的就是全局注册的组件,而实例上的components就是局部注册的组件,最后两者合在一起,就是所有注册的组件!


fernandou
1 声望0 粉丝

最近想写点东西了