Vue源碼解讀之Component組件注冊的實現(xiàn)
什么是組件?
組件 (Component) 是 Vue.js 最強(qiáng)大的功能之一。組件可以擴(kuò)展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以表現(xiàn)為用 is 特性進(jìn)行了擴(kuò)展的原生 HTML 元素。
所有的 Vue 組件同時也都是 Vue 的實例,所以可接受相同的選項對象 (除了一些根級特有的選項) 并提供相同的生命周期鉤子。
Vue可以有全局注冊和局部注冊兩種方式來注冊組件。
全局注冊
注冊方式
全局注冊有以下兩種注冊方式:
通過Vue.component 直接注冊。
Vue.component('button-counter', {
//data選項必須是一個函數(shù)
data: function () {
return {
count: 0
}
},
template:'#clickBtn'
})
通過Vue.extend來注冊。
var buttonComponent = Vue.extend({
name:'button-counter',
data: function () {
return {
count: 0
}
},
template:'#clickBtn'
});
Vue.component('button-counter', buttonComponent);
具體過程
Vue初始化時,initGlobalAPI通過調(diào)用initAssetRegisters()進(jìn)行組件注冊。
function initAssetRegisters (Vue) {
// 創(chuàng)建組件注冊的方法
// ASSET_TYPES在Vue內(nèi)部定義,var ASSET_TYPES = ['component','directive','filter'];
ASSET_TYPES.forEach(function (type) {
Vue[type] = function (
id,
definition
) {
//這里的definition指的是定義(Function或Object),是函數(shù)或者對象
//如果definition不存在,直接返回options內(nèi)type和id對應(yīng)的
//這里的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方法)將定義對象轉(zhuǎn)化為構(gòu)造器。
//Vue.options._base = Vue;
definition = this.options._base.extend(definition);
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition };
}
// 將構(gòu)造器賦值給 this.options[‘component'+ 's'][id]
//全局的組件,指令和過濾器,統(tǒng)一掛在vue.options上。在init的時候利用mergeOptions合并策略侵入實例,供實例使用。
this.options[type + 's'][id] = definition;
return definition
}
};
});
}
圖一:

initAssetRegisters里面通過this.options._base.extend方法將定義對象轉(zhuǎn)化為構(gòu)造器,而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 = {});
//如果組件已經(jīng)被緩存在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上的所有屬性和方法。
//關(guān)于 prototype的學(xué)習(xí):http://www.cnblogs.com/dolphinX/p/3286177.html
Sub.prototype = Object.create(Super.prototype);
//Sub構(gòu)造函數(shù)修正,學(xué)習(xí)于https://www.cnblogs.com/SheilaSun/p/4397918.html
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
//通過vue的合并策略合并添加項到新的構(gòu)造器上
Sub.options = mergeOptions(
Super.options,
extendOptions
);
//緩存父構(gòu)造器
Sub['super'] = Super;
// 處理props和computed響應(yīng)式配置項
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;
//在新的構(gòu)造器上掛上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);
//緩存組件構(gòu)造器在extendOptions上
cachedCtors[SuperId] = Sub;
return Sub
};
vue.extend返回了一個帶有附加Option的vue構(gòu)造器。這個構(gòu)造器被命名為Sub,等待render時候初始化。
initAssetRegisters完成之后,options下掛載了全局組件button-counter,如圖:

接下來調(diào)用new Vue()渲染vue整體的生命周期
局部注冊
如果不需要全局注冊,或者是讓組件使用在其它組件內(nèi),可以用選項對象的components屬性實現(xiàn)局部注冊。
注冊方式
new Vue({
el: '#components-demo',
components:{
'button-counter':{
template:'#clickBtn',
data: function () {
return {
count: 0
}
}
}
}
})
具體過程
Vue局部組件注冊也是通過initAssetRegisters()方法調(diào)用Vue.extend,不同的是在createComponent()時,initMixin()里面有判斷
if (options && options._isComponent) {
//因為Vue動態(tài)合并策略非常慢,并且內(nèi)部組件的選項都不需要特殊處理。
//調(diào)用initInternalComponent快捷方法,內(nèi)部組件實例化。
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);
// 這樣做是因為它比動態(tài)枚舉更快。
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的結(jié)構(gòu)如圖所示:

局部注冊和全局注冊不同的是,只有該類型的組件才可以訪問局部注冊的子組件,而全局注冊是擴(kuò)展到 Vue.options 下。在所有組件創(chuàng)建的過程中,都會從全局的 Vue.options.components 擴(kuò)展到當(dāng)前組件的 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>
可參考:http://www.dbjr.com.cn/article/144050.htm
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue3動態(tài)路由(響應(yīng)式帶參數(shù)的路由)變更頁面不刷新的問題解決辦法
問題來源是因為我的開源項目Maple-Boot項目的網(wǎng)站前端,因為項目主打的內(nèi)容發(fā)布展示,所以其中的內(nèi)容列表頁會根據(jù)不同的菜單進(jìn)行渲染不同的路由,本文降介紹Vue3動態(tài)路由變更頁面不刷新的問題解決辦法,需要的朋友可以參考下2024-07-07

