Vue源碼解讀之Component組件註冊

Vue能夠有全局註冊和局部註冊兩種方式來註冊組件。javascript

全局註冊

註冊方式

全局註冊有如下兩種註冊方式:html

  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()進行組件註冊。vue

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.pngjava

initAssetRegisters裏面經過this.options._base.extend方法將定義對象轉化爲構造器,而options._base.extend其實就是Vue.extend。接下來咱們就看一下Vue.extend作了什麼。node

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,如圖:segmentfault

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...

相關文章
相關標籤/搜索