所有的 Vue 組件都是 Vue 實例,並且接受相同的選項對象
先說結論
這句話,這是官方文檔說的
不過嚴謹來說,應該是
一個單頁應用就是一個 vue 的實例
每個自定義組件就是一個 vueComponent 實例,
只不過 vueComponent 的構造器和 vue 的構造器的內容是基本一樣的。效果表現也一致,兩者對比如下
<div id="app">{{uname}}</div>
<script>
// 僅有Vue實例
var app = new Vue({
el: "#app",
data() {
return {
uname: "小紅",
};
},
methods: {
say() {
console.log("調用了say方法");
},
},
mounted() {
this.say();
},
});
</script>
<div id="app"></div>
<script>
// 組件
const RootCmp = {
data() {
return {
uname: "小紅",
};
},
methods: {
say() {
console.log("調用了say方法");
},
},
mounted() {
this.say();
},
template: "<div>{{uname}}</div>",
};
var app = new Vue({
el: "#app",
render: (h) => h(RootCmp),
});
</script>
證明
我們創建個 vue 項目
<div id="app">
{{ message }}
<cmp-one></cmp-one>
------
<cmp-two></cmp-two>
</div>
<script>
// 全局組件
Vue.component("CmpOne", {
template: "<div>組件1</div>",
});
var app = new Vue({
el: "#app",
data: {
message: "Hello Vue!",
},
components: {
CmpTwo: {
// 局部組件
template: "<div>組件2</div>",
},
},
});
</script>
然后開始分析源碼
以下是 vue.js 部分代碼,
我們很快找到 Vue 的類,並添加個 log
function Vue(options) {
console.log("Vue構造類");
if (!(this instanceof Vue)) {
warn("Vue is a constructor and should be called with the `new` keyword");
}
this._init(options);
}
結果發現,Vue 構造函數只會被執行一次,也就是你手動 new 的時候。
這足以證明每個組件(或.vue)文件不是 Vue 的實例。
那組件是誰的實例呢?
我們繼續看源碼,通過代碼分析我們得知
全局組件 or 局部組件的注冊初始化,是走這里
...
var Sub = function VueComponent (options) {
console.log('VueComponent組件的構造類');
this._init(options);
};
...
// 其中vnode.componentOptions.Ctor就是VueComponent類
new vnode.componentOptions.Ctor(options);
如上代碼中,VueComponent組件的構造類
會被打印兩次。
這也就是說,它才是組件實例的構造函數(類)。
源碼稍微深度分析
Vue.extend = function (extendOptions, is) {
var Super = this;
// ...
var Sub = function VueComponent(options) {
console.log("VueComponent組件的構造用來 實例化");
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
// ...
return Sub;
};
function initAssetRegisters(Vue) {
// ...
ASSET_TYPES.forEach(function (type) {
Vue[type] = function (id, definition) {
if (!definition) {
return this.options[type + "s"][id];
} else {
if (type === "component" && isPlainObject(definition)) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition);
}
return definition;
}
};
});
}
function initGlobalAPI(Vue) {
// ...
initAssetRegisters(Vue);
}
initGlobalAPI(Vue);
解讀:
項目初始化進來的時候會執行一個全局方法 initGlobalAPI
initGlobalAPI里會調用initAssetRegisters
initAssetRegisters的作用是給你定義的一些全局Asset
擴展一些屬性
枚舉ASSET_TYPES 就是全局Asset
:包含 component,directive,filter
比如我的組件 CmpOne,原本只有 template 屬性,經過擴展后,就會多出如下屬性
{
cid: 1
component: ƒ ( id, definition )
directive: ƒ ( id, definition )
extend: ƒ (extendOptions, is)
extendOptions: {template: '<div>組件1</div>', name: 'CmpOne', _Ctor: {…}}
filter: ƒ ( id, definition )
mixin: ƒ (mixin)
options: {components: {…}, directives: {…}, filters: {…}, template: '<div>組件1</div>', _base: ƒ, …}
sealedOptions: {components: {…}, directives: {…}, filters: {…}, template: '<div>組件1</div>', _base: ƒ, …}
super: ƒ Vue(options)
superOptions: {components: {…}, directives: {…}, filters: {…}, _base: ƒ}
use: ƒ (plugin)
}
其中重要的一點就是給CmpOne組件指定了構造器
那是怎么指定的呢?
this.options._base.extend(definition);
這句代碼本質上就是調用了Vue.extend
來創建了一個Vue的子類。
那么情況就明了了,組件構造函數是繼承自Vue構造。
最后vue在創建vdom的時候,就會去實例化組件類,即new VueComponent()
總結
Vue組件構造類
是繼承自Vue類
- 它們的屬性options也基本是一樣的,Vue 實例會比 Vue 組件多出 el 和 router 屬性(對應官方說辭:組件是可復用的 Vue 實例,所以它們與 new Vue 接收相同的選項,例如 data、computed、watch、methods 以及生命周期鈎子等。僅有的例外是像 el 這樣根實例特有的選項。)
所有的Vue組件都是Vue實例
這句話只是官方為了方便咱們理解,但嚴格來說並不對,嚴格來說應該是所有的Vue組件都是Vue的子類實例
- 在多頁應用下或vue2的
Vue.extend
和vue3的createApp
,才會形成多個Vue實例
擴展
Vue.component和Vue.extend的聯系:
Vue.component注冊或獲取全局組件。注冊還會自動使用給定的 id 設置組件的名稱,如上id就是組件名CmpOne,內部實質上調用了Vue.extend,最后返回"子類"(構造器),這個子類構造器。