Vue源码解读之Component组件注册

Vue可以有全局注册和局部注册两种方式来注册组件。

全局注册

注册方式

全局注册有以下两种注册方式:

  1. 通过Vue.component 直接注册。
Vue.component('button-counter', {
        //data选项必须是一个函数
        data: function () {
            return {
                count: 0
            }
        },
        template:'#clickBtn'
    })
  1. 通过Vue.extend来注册。
 var buttonComponent = Vue.extend({
        name:'button-counter',
        data: function () {
            return {
                count: 0
            }
        },
        template:'#clickBtn'
    });
 Vue.component('button-counter', buttonComponent);

具体过程

Vue初始化时,initGlobalAPI通过调用initAssetRegisters()进行组件注册。

function initAssetRegisters (Vue) {
  // 创建组件注册的方法
  // ASSET_TYPES在Vue内部定义,var ASSET_TYPES = ['component','directive','filter'];
  ASSET_TYPES.forEach(function (type) {
    Vue[type] = function (
      id,
      definition
    ) {
      //这里的definition指的是定义(Function或Object),是函数或者对象
      //如果definition不存在,直接返回options内type和id对应的
      //这里的options是指全局的组件,指令和过滤器,见图一
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if ("development" !== 'production' && type === 'component') {
          validateComponentName(id);
        }
        // 如果是component(组件)方法,并且definition是对象
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id;
          //通过this.options._base.extend方法(也就是Vue.extend方法)将定义对象转化为构造器。
          //Vue.options._base = Vue;
          definition = this.options._base.extend(definition);
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition };
        }
        // 将构造器赋值给 this.options[‘component’+ 's'][id]
        //全局的组件,指令和过滤器,统一挂在vue.options上。在init的时候利用mergeOptions合并策略侵入实例,供实例使用。
        this.options[type + 's'][id] = definition;
        return definition
      }
    };
  });
}

图一:
clipboard.png

initAssetRegisters里面通过this.options._base.extend方法将定义对象转化为构造器,而options._base.extend其实就是Vue.extend。接下来我们就看一下Vue.extend做了什么。

Vue.extend = function (extendOptions) {
    extendOptions = extendOptions || {};
    var Super = this;
    var SuperId = Super.cid;
    //组件缓存
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
    //如果组件已经被缓存在extendOptions上则直接取出
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    //如果有name属性,检验name拼写是否合法
    var name = extendOptions.name || Super.options.name;
    if ("development" !== 'production' && name) {
      validateComponentName(name);
    }

    var Sub = function VueComponent (options) {
      this._init(options);
    };
    //将vue上原型的方法挂在Sub.prototype中,Sub的实例同时也继承了vue.prototype上的所有属性和方法。
    //关于 prototype的学习:http://www.cnblogs.com/dolphinX/p/3286177.html
    Sub.prototype = Object.create(Super.prototype);
    //Sub构造函数修正,学习于https://www.cnblogs.com/SheilaSun/p/4397918.html
    Sub.prototype.constructor = Sub;
    Sub.cid = cid++;
    //通过vue的合并策略合并添加项到新的构造器上
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );
    //缓存父构造器
    Sub['super'] = Super;

    // 处理props和computed响应式配置项
    if (Sub.options.props) {
      initProps$1(Sub);
    }
    if (Sub.options.computed) {
      initComputed$1(Sub);
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend;
    Sub.mixin = Super.mixin;
    Sub.use = Super.use;

    //在新的构造器上挂上vue的工具方法
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type];
    });
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub;
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    Sub.sealedOptions = extend({}, Sub.options);

    //缓存组件构造器在extendOptions上
    cachedCtors[SuperId] = Sub;
    return Sub
  };

vue.extend返回了一个带有附加Option的vue构造器。这个构造器被命名为Sub,等待render时候初始化。
initAssetRegisters完成之后,options下挂载了全局组件button-counter,如图:

clipboard.png

接下来调用new Vue()渲染vue整体的生命周期,详情请看,https://segmentfault.com/a/11...

局部注册

如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的components属性实现局部注册。

注册方式

new Vue({
        el: '#components-demo',
        components:{
            'button-counter':{
                template:'#clickBtn',
                data: function () {
                    return {
                        count: 0
                    }
                }
            }
        }
    })

具体过程

Vue局部组件注册也是通过initAssetRegisters()方法调用Vue.extend,不同的是在createComponent()时,initMixin()里面有判断

if (options && options._isComponent) {
      //因为Vue动态合并策略非常慢,并且内部组件的选项都不需要特殊处理。
      //调用initInternalComponent快捷方法,内部组件实例化。
      initInternalComponent(vm, options);
 }
 else {
     vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
function initInternalComponent (vm, options) {
  var opts = vm.$options = Object.create(vm.constructor.options);
  // 这样做是因为它比动态枚举更快。
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;

  var vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;

  if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
}

opts的结构如图所示:

clipboard.png

局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 Vue.options 下。在所有组件创建的过程中,都会从全局的 Vue.options.components 扩展到当前组件的 vm.$options.components 下,这就是全局注册的组件能被任意使用的原因。

组件名定义

定义组件名的方式有两种:

  1. 使用短横线形式
Vue.component('button-counter', {})

引用这个自定义元素时,必须用 <button-counter></button-counter>

  1. 使用驼峰的形式
Vue.component('buttonCounter', {  })

此时在引用这个自定义元素时,两种命名方法都可以使用。也就是说,<buttonCounter><button-counter> 都是可行的。
注意,直接在 DOM (即非字符串的模板) 中使用时只有短横线是有效的。如下:

<div id="components-demo">
        <button-counter></button-counter>
</div>

可参考:https://segmentfault.com/a/11...

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值