Vue能夠有全局註冊和局部註冊兩種方式來註冊組件。javascript
全局註冊有如下兩種註冊方式:html
Vue.component('button-counter', { //data選項必須是一個函數 data: function () { return { count: 0 } }, template:'#clickBtn' })
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 } }; }); }
圖一:
java
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
接下來調用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的結構如圖所示:學習
局部註冊和全局註冊不一樣的是,只有該類型的組件才能夠訪問局部註冊的子組件,而全局註冊是擴展到 Vue.options 下。在全部組件建立的過程當中,都會從全局的 Vue.options.components 擴展到當前組件的 vm.$options.components 下,這就是全局註冊的組件能被任意使用的緣由。
定義組件名的方式有兩種:
Vue.component('button-counter', {})
引用這個自定義元素時,必須用 <button-counter></button-counter>
Vue.component('buttonCounter', { })
此時在引用這個自定義元素時,兩種命名方法均可以使用。也就是說,<buttonCounter>
和 <button-counter>
都是可行的。
注意,直接在 DOM (即非字符串的模板) 中使用時只有短橫線是有效的。以下:
<div id="components-demo"> <button-counter></button-counter> </div>