Vue3組件掛載之創(chuàng)建組件實(shí)例詳解
前情提要
上文我們講解了執(zhí)行createApp(App).mount('#root')中的mount函數(shù),我們分析了創(chuàng)建虛擬節(jié)點(diǎn)的幾個(gè)方法,以及setRef的執(zhí)行機(jī)制、本文我們繼續(xù)講解mountComponent,掛載組件的流程。
本文主要內(nèi)容
createComponentInstance發(fā)生了什么?- 如何標(biāo)準(zhǔn)化組件定義的
props、emits? - 為什么
provide提供的值子組件都能訪問(wèn)到? 組件的v-model實(shí)現(xiàn)原理、組件v-model修飾符實(shí)現(xiàn)原理。- 組件
emit實(shí)現(xiàn)原理。 once修飾符實(shí)現(xiàn)原理。- 幾乎
所有組件實(shí)例屬性的詳細(xì)講解。
mountComponent
- 這個(gè)方法主要用于
掛載組件,根據(jù)傳遞的虛擬節(jié)點(diǎn)創(chuàng)建組件實(shí)例,調(diào)用setupComponent函數(shù)進(jìn)行初始化組件實(shí)例的插槽和props,如果是有狀態(tài)組件還需要處理setup的返回值。最后調(diào)用setupRenderEffect綁定副作用更新函數(shù)。這個(gè)函數(shù)比較簡(jiǎn)單,我們將重心放到createComponentInstance、setupComponent、setupRenderEffect當(dāng)中、后文也是圍繞這三個(gè)方法進(jìn)行依次講解。
const mountComponent = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
//創(chuàng)建組件實(shí)例
const instance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
));
setupComponent(instance);
if (instance.asyncDep) {
//處理異步邏輯,suspense的時(shí)候在進(jìn)行講解
}
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
);
};
創(chuàng)建組件實(shí)例
createComponentInstance: 根據(jù)虛擬節(jié)點(diǎn)創(chuàng)建組件實(shí)例的方法,簡(jiǎn)單的說(shuō)就是創(chuàng)建一個(gè)instance對(duì)象,這個(gè)上面有許多的屬性用來(lái)描述當(dāng)前這個(gè)組件以及存儲(chǔ)需要后續(xù)使用到的屬性,所以我們主要講解各個(gè)屬性的作用,我們先來(lái)看看這個(gè)函數(shù)的源碼。uid: 標(biāo)識(shí)當(dāng)前實(shí)例的id,這是一個(gè)從0開始的遞增值,同時(shí)他也是Vue調(diào)度器的優(yōu)先級(jí)值,越小的值優(yōu)先級(jí)越高,試想一下,父組件先掛載,然后才會(huì)掛載子節(jié)點(diǎn),子節(jié)點(diǎn)如果包含組件,那么uid的值將會(huì)比父組件的uid值大,當(dāng)父組件掛載完畢,父組件的兄弟組件開始掛載,那么uid將會(huì)更大,這樣也符合流程更新邏輯,關(guān)于調(diào)度器我們之后單獨(dú)開一章節(jié)來(lái)進(jìn)行講解。vnode: 實(shí)例的虛擬節(jié)點(diǎn)。parent: 當(dāng)前組件實(shí)例的父組件實(shí)例。appContext:createApp的時(shí)候注入的一個(gè)上下文,我們通過(guò)app.component,app.mixin調(diào)用后緩存的component和mixin都放在這里面,如果沒(méi)有parent表示當(dāng)前是根組件,那么就是這個(gè)appContext,如果有parent則繼承parent的appContext。我們可以通過(guò)這里發(fā)現(xiàn)所有的組件的appContext都會(huì)是唯一的一個(gè)context。因此所有組件實(shí)例都將能訪問(wèn)到全局注冊(cè)的component、mixin、directive等。我們來(lái)回顧一下它的結(jié)構(gòu)(省略了部分屬性)。
{
app: null,
config: {
globalProperties: {},
optionMergeStrategies: {},
compilerOptions: {},
},
mixins: [],
components: {},
directives: {},
};
next:組件更新有兩種形式,一種是組件內(nèi)部的狀態(tài)發(fā)生了改變,引起了組件自身更新;另外一種是父組件傳遞了props,props改變,父組件更新、導(dǎo)致子組件引起了更新。第一種next是null。如果是第二種,那么需要給next賦值為當(dāng)前子組件最新的VNode(虛擬節(jié)點(diǎn))。而next將會(huì)用于更新組件的props以及slots等屬性。組件自身狀態(tài)改變引起的更新是不需要更新props的。subTree: 調(diào)用render函數(shù)之后返回的組件要渲染的虛擬根節(jié)點(diǎn),可以通過(guò)這個(gè)屬性讀取到組件需要渲染的所有虛擬節(jié)點(diǎn)。effect: 在setupRenderEffect中創(chuàng)建的reactiveEffect,我們?cè)?code>Vue3源碼分析(2)中講解了reactiveEffect,不了解的可以在閱讀一下,目前簡(jiǎn)單理解為在調(diào)用render函數(shù)的時(shí)候收集依賴,如果響應(yīng)式的值發(fā)生了改變會(huì)重新調(diào)用update函數(shù)執(zhí)行更新流程。我們將會(huì)在講解setupRenderEffect中詳細(xì)講解這一部分。update:組件更新函數(shù),只要調(diào)用了這個(gè)函數(shù),組件就會(huì)強(qiáng)制更新。同時(shí)響應(yīng)式發(fā)生改變調(diào)用的也是這個(gè)函數(shù)。render:渲染函數(shù)。可以通過(guò)編譯<template></template>得到,也可以通過(guò)寫template屬性獲得,也可以通過(guò)自己寫render函數(shù)。exposed: 組件指定的暴露到外部的屬性。provides: 用戶在組件中設(shè)置了provide,子代組件可以通過(guò)inject收到傳遞的值,provides:parent ? parent.provides : Object.create(appContext.provides)通過(guò)這段代碼分析我們可以知道,provides中至少含有全局提供的provides,如果當(dāng)前組件提供了provide,后面會(huì)將其混合,并且繼承自父組件的provides,這也就解釋了為什么provides可以向下傳遞,因?yàn)槊恳粚佣伎梢允盏?code>本組件的provides和父組件的provides并進(jìn)行合并。components: 如果你想在template中使用組件,需要在這里注冊(cè),對(duì)于使用了<script setup>語(yǔ)法糖的會(huì)將其編譯到components屬性當(dāng)中,當(dāng)然這個(gè)屬性就是用來(lái)存放這些注冊(cè)的組件的。directives: 存放自定義指令。propsOptions:合并了mixins和extends后的props,這里的屬性為什么是propsOptions而不是props呢?這個(gè)VNode中的props又有什么關(guān)系呢?實(shí)際上propsOptions代表的是用戶在組件內(nèi)定義的props,而VNode中的props是外部傳遞給組件的props。這一點(diǎn)要加以區(qū)分。同時(shí)這里調(diào)用了normalizePropsOptions來(lái)對(duì)propsOptions進(jìn)行標(biāo)準(zhǔn)化。接下來(lái)我們分析一下normalizePropsOptions函數(shù)。這個(gè)函數(shù)比較長(zhǎng)我們分成三個(gè)部分來(lái)分析。
- 首先從
appContext中獲取props緩存,避免處理過(guò)了組件props重復(fù)處理。如果comp不是一個(gè)函數(shù),這個(gè)判斷是因?yàn)椋?code>Vue3中允許函數(shù)組件的存在,但是函數(shù)組件是無(wú)狀態(tài)組件也沒(méi)有props,所以不做處理。那么剩下的都是有狀態(tài)組件了,這里會(huì)處理全局注冊(cè)的mixins,組件本身的mixins和extends。我們可以發(fā)現(xiàn)全局的最先處理,所以全局注冊(cè)的mixins的優(yōu)先級(jí)將會(huì)是最低的,其次是extends,顯然優(yōu)先級(jí)最高的則是組件自身的mixins,因?yàn)樗詈髨?zhí)行,那么shared.extend將會(huì)最后覆蓋之前的props。我們還可以發(fā)現(xiàn)extends屬性和mixins屬性在實(shí)現(xiàn)上沒(méi)有任何區(qū)別,只是mixins可以是數(shù)組,而extends不能是數(shù)組。最后說(shuō)一下asMixin,我們知道全局的mixins只需要合并一次,但是normalizePropsOptions會(huì)調(diào)用多次,為了避免全局屬性混合的多次執(zhí)行,設(shè)置了asMixin這個(gè)參數(shù)。當(dāng)asMixin為true的時(shí)候表示不需要在合并全局的mixins了。特別提示:shared.extend就是Object.assign。
function normalizePropsOptions(comp, appContext, asMixin = false) {
//獲取props的緩存
const cache = appContext.propsCache;
const cached = cache.get(comp);
//這個(gè)緩存是一個(gè)type對(duì)應(yīng)一個(gè)[normalized, needCastKeys]
//normalized表示合并了mixins和extends后的props
if (cached) {
return cached;
}
const raw = comp.props;
const normalized = {};
const needCastKeys = [];
let hasExtends = false;
if (!shared.isFunction(comp)) {
//用于合并props的函數(shù),因?yàn)閑xtends和mixins
//中還可以寫mixins和extends所以需要遞歸合并
/**
* 例如const mixins = [{
* extends:{},
* mixins:[{props}],
* props
* }]
*/
const extendProps = (raw) => {
hasExtends = true;
const [props, keys] = normalizePropsOptions(raw, appContext, true);
//normalized為合并后的props
shared.extend(normalized, props);
if (keys) needCastKeys.push(...keys);
};
//首先合并全局注冊(cè)的mixins中的props屬性(最先合并的優(yōu)先級(jí)最低)
if (!asMixin && appContext.mixins.length) {
appContext.mixins.forEach(extendProps);
}
//然后合并extends屬性(中間合并的優(yōu)先級(jí)居中)
//(extends功能與mixins幾乎一樣)但是更注重于繼承
//并且extends不能是數(shù)組
if (comp.extends) {
extendProps(comp.extends);
}
//最后合并組件自身的mixins(最后合并的優(yōu)先級(jí)最高)
if (comp.mixins) {
comp.mixins.forEach(extendProps);
}
}
//省略第二部分的代碼...
}
- 我們知道這個(gè)函數(shù)主要是對(duì)
propsOptions進(jìn)行標(biāo)準(zhǔn)化,簡(jiǎn)單的說(shuō)就是將各式各樣的propsOptions統(tǒng)一成唯一標(biāo)準(zhǔn)。當(dāng)傳遞的props:['msg']數(shù)組形式的時(shí)候,我們需要將其轉(zhuǎn)化為props:{msg:{}}。validatePropName用于檢測(cè)key是否合法,Vue中,組件傳遞參數(shù)不能以$開頭定義數(shù)據(jù)。例如$msg就是不合法的。
function normalizePropsOptions(comp, appContext, asMixin = false) {
//省略第一部分的代碼...
//如果沒(méi)有props且沒(méi)有全局的mixins
//組件本身的mixins、extends則設(shè)置
//當(dāng)前實(shí)例的props緩存為空
if (!raw && !hasExtends) {
if (shared.isObject(comp)) {
cache.set(comp, shared.EMPTY_ARR);
}
return shared.EMPTY_ARR;
}
//處理這種類型props:['msg','hello']
if (shared.isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
if (!shared.isString(raw[i])) {
console.warn(
`props must be strings when using array syntax. ${raw[i]}`
);
}
//將v-data-xxx轉(zhuǎn)化為駝峰式vDataXxx
//但是不能以$開頭
const normalizedKey = shared.camelize(raw[i]);
if (validatePropName(normalizedKey)) {
//將其變?yōu)閜rops:{"msg":{}}
normalized[normalizedKey] = shared.EMPTY_OBJ;
}
}
}
//省略第三部分的代碼...
}
- 在上面的代碼中頻繁出現(xiàn)
needCastKeys,這個(gè)代表的是需要特殊處理的key,例如:props:{msg:{default:"msg"}}含有default,那么理論上我們應(yīng)當(dāng)判斷傳遞的屬性值是否存在,然后在決定是否使用default的值,但是這里我們僅進(jìn)行標(biāo)準(zhǔn)化,所以對(duì)于含有default屬性的我們需要單獨(dú)放入needCastKeys中,便于后面對(duì)props中的處理。再比如說(shuō)<Comp yes></Comp>傳遞了yes屬性,在propsOptions中props:{yes:{type:Boolean}}這樣的key=>"yes"也是需要處理的,yes的值應(yīng)該為true,所以對(duì)于type中含有Boolean的也需要放入needCastKeys中。
export function normalizePropsOptions(comp, appContext, asMixin = false) {
//省略第二部分代碼...
if (shared.isArray(raw)) {
//省略...
}
//處理props:{msg:String}
else if (raw) {
if (!shared.isObject(raw)) {
warn(`invalid props options`, raw);
}
//循環(huán)遍歷所有的key
for (const key in raw) {
//"v-data-xxx"=>"vDataXxx"變?yōu)樾●劮迨?
const normalizedKey = shared.camelize(key);
//檢驗(yàn)key是否合法
if (validatePropName(normalizedKey)) {
const opt = raw[key]; //獲取value
//如果獲取的value是數(shù)組或函數(shù)轉(zhuǎn)化則為{type:opt}
//props:{"msg":[]||function(){}}=>
//props:{"msg":{type:msg的值}}
const prop = (normalized[normalizedKey] =
shared.isArray(opt) || shared.isFunction(opt) ? { type: opt } : opt);
if (prop) {
//找到Boolean在prop.type中的位置 Boolean,["Boolean"]
const booleanIndex = getTypeIndex(Boolean, prop.type);
//找到String在prop.type中的位置
const stringIndex = getTypeIndex(String, prop.type);
prop[0] = booleanIndex > -1; //type中是否包含Boolean
//type中不包含String或者Boolean的位置在String前面
//例如:"msg":{type:["Boolean","String"]}
prop[1] = stringIndex < 0 || booleanIndex < stringIndex;
//如果有default屬性,或者type中包含Boolean放入needCastKeys中
if (booleanIndex > -1 || shared.hasOwn(prop, "default")) {
needCastKeys.push(normalizedKey);
}
}
}
}
}
const res = [normalized, needCastKeys];
//設(shè)置緩存
if (shared.isObject(comp)) {
cache.set(comp, res);
}
return res; //返回合并后的normalized
}
emitsOptions:合并了mixins和extends后的emits屬性。并且對(duì)emits進(jìn)行了標(biāo)準(zhǔn)化。主要是調(diào)用了normalizeEmitsOptions進(jìn)行處理,這個(gè)函數(shù)的邏輯和normlizePropsOptions非常相似,如果你看懂了上述的解釋,這個(gè)函數(shù)相信你很容易就能看懂,這里就不在做多余的解釋了。
export function normalizeEmitsOptions(comp, appContext, asMixin = false) {
//獲取appContext中的緩存
const cache = appContext.emitsCache;
const cached = cache.get(comp);
//如果已經(jīng)讀取過(guò)返回緩存
if (cached !== undefined) {
return cached;
}
//獲取組件的emits屬性{emits:['']}
//還可以對(duì)象寫法{emits:{'':null||function(){}}}
const raw = comp.emits;
//最終的合并對(duì)象
let normalized = {};
let hasExtends = false;
if (!shared.isFunction(comp)) {
//合并emits的方法
const extendEmits = (raw) => {
const normalizedFromExtend = normalizeEmitsOptions(raw, appContext, true);
if (normalizedFromExtend) {
hasExtends = true;
shared.extend(normalized, normalizedFromExtend);
}
};
//最先合并appContext中的(優(yōu)先級(jí)最低)
if (!asMixin && appContext.mixins.length) {
appContext.mixins.forEach(extendEmits);
}
//然后合并實(shí)例的extends(優(yōu)先級(jí)居中)
if (comp.extends) {
extendEmits(comp.extends);
}
//最后合并組件自身的(優(yōu)先級(jí)最高)
if (comp.mixins) {
comp.mixins.forEach(extendEmits);
}
}
if (!raw && !hasExtends) {
//設(shè)置緩存
if (shared.isObject(comp)) {
cache.set(comp, null);
}
return null;
}
//即使emits:[]是數(shù)組最終也會(huì)被轉(zhuǎn)化為對(duì)象
//emits:['m']=>emits:{'m':null}
if (shared.isArray(raw)) {
raw.forEach((key) => (normalized[key] = null));
} else {
//合并
shared.extend(normalized, raw);
}
if (shared.isObject(comp)) {
cache.set(comp, normalized);
}
return normalized;
}
attrs: 如果你給組件傳遞了沒(méi)有在組件內(nèi)部聲明的屬性,那么將會(huì)放入attrs中。例如:
<Comp id="a"></Comp>
//Comp組件 沒(méi)有對(duì)id的聲明,那么會(huì)放入attrs中
export default {
props:{}
}
setupState:setup的返回值。ctx:當(dāng)前組件的上下文。
- 通過(guò)
createDevRenderContext函數(shù)創(chuàng)建。在這個(gè)函數(shù)當(dāng)中,對(duì)ctx對(duì)象進(jìn)行了代理,可以通過(guò)ctx._訪問(wèn)到組件的實(shí)例,同時(shí)將publicPropertiesMap上所有的屬性代理到了ctx上,簡(jiǎn)單的說(shuō)就是之前需要通過(guò)publicPropertiesMap訪問(wèn)屬性,現(xiàn)在在ctx上同樣能訪問(wèn)到。這樣的代理方式在Vue中被大量采用,setupState methods data computed watch props當(dāng)中所有的屬性都將會(huì)采用這種方式被代理到ctx上,當(dāng)然會(huì)出現(xiàn)重名問(wèn)題,所以保證上述的這些屬性中,應(yīng)當(dāng)避免重名,否則會(huì)有警告提示。當(dāng)然這些屬性是是在哪里被代理到ctx上的,我們后面都會(huì)講到。
function createDevRenderContext(instance) {
const target = {};
//可通過(guò)_訪問(wèn)實(shí)例對(duì)象
Object.defineProperty(target, `_`, {
configurable: true,
enumerable: false,
get: () => instance,
});
Object.keys(publicPropertiesMap).forEach((key) => {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
get: () => publicPropertiesMap[key](instance),
set: shared.NOOP,
});
});
return target;
}
- 不知道這個(gè)這個(gè)對(duì)象上的方法你是否很熟悉呢?沒(méi)錯(cuò),這就是
Vue官方展示的組合式Api,你可以在setup中訪問(wèn)this調(diào)用到這些方法,不妨大膽猜測(cè)一下,為什么在setup中訪問(wèn)this能讀取的這些方法?其實(shí)很簡(jiǎn)單。setup.call(instance.ctx,其他參數(shù)),實(shí)際上只要能訪問(wèn)到this的地方,基本上都是訪問(wèn)的instance.ctx、當(dāng)然有些地方的this綁定的是instance.proxy。這兩者的區(qū)別我們?cè)诤竺嬖谶M(jìn)行講解。其中的i代表的是組件實(shí)例。
const publicPropertiesMap = shared.extend(Object.create(null), {
$: (i) => i, //獲取當(dāng)前實(shí)例
$el: (i) => i.vnode.el,
$data: (i) => i.data, //獲取實(shí)例的data
$props: (i) => reactivity.shallowReadonly(i.props), //獲取props
$attrs: (i) => reactivity.shallowReadonly(i.attrs),
$slots: (i) => reactivity.shallowReadonly(i.slots),
$refs: (i) => reactivity.shallowReadonly(i.refs),
$emit: (i) => i.emit,
//獲取options
$options: (i) => resolveMergedOptions(i),
//強(qiáng)制更新
$forceUpdate: (i) => i.f || (i.f = () => queueJob(i.update)),
// $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)),
$watch: (i) => instanceWatch.bind(i),
$parent: (i) => getPublicInstance(i.parent),
$root: (i) => getPublicInstance(i.root),
});
emit: 觸發(fā)傳遞給組件的函數(shù)。通過(guò)this.$emit調(diào)用的就是這個(gè)函數(shù)。對(duì)于emit這個(gè)方法我們同樣分成三個(gè)部分進(jìn)行講解。
- 之前我們?cè)敿?xì)講解了
emitsOptions,他最終會(huì)被標(biāo)準(zhǔn)化。這也是標(biāo)準(zhǔn)化的作用,后續(xù)的處理都會(huì)變得很簡(jiǎn)單,而不需要兼容多種寫法。如果調(diào)用了emit方法,但是在emitsOptions中沒(méi)有這個(gè)屬性,表示并沒(méi)有注冊(cè),需要警告用戶,同時(shí)如果你傳遞的emits是一個(gè)函數(shù),那么他就是一個(gè)檢驗(yàn)函數(shù)(如果不理解請(qǐng)查看Vue官網(wǎng)對(duì)于emits的解釋),傳遞的參數(shù)為調(diào)用emits傳遞的剩余參數(shù)。
function emit(instance, event, ...rawArgs) {
//已經(jīng)卸載無(wú)須在執(zhí)行
if (instance.isUnmounted) return;
//獲取props
const props = instance.vnode.props || shared.EMPTY_OBJ;
//獲取經(jīng)過(guò)標(biāo)準(zhǔn)化的emits和props
//emits:{方法名:null||function(){}}
//如果為function代表的是驗(yàn)證函數(shù)
const {
emitsOptions,
propsOptions: [propsOptions],
} = instance;
if (emitsOptions) {
//警告用戶:調(diào)用了emit 但是沒(méi)有在emitOptions中找到,代表沒(méi)有聲明
if (!(event in emitsOptions)) {
if (!propsOptions || !(shared.toHandlerKey(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
`the emits option nor as an "${shared.toHandlerKey(event)}" prop.`
);
}
}
//獲取驗(yàn)證函數(shù)
else {
const validator = emitsOptions[event];
if (shared.isFunction(validator)) {
//調(diào)用驗(yàn)證函數(shù),返回false則警告
const isValid = validator(...rawArgs);
if (!isValid) {
warn(
`Invalid event arguments: event validation failed for event "${event}".`
);
}
}
}
}
//省略第二部分代碼...
}
- 這里的處理可能會(huì)讓你很懵逼,這個(gè)
isModelListener是個(gè)啥?modifiersKey又是個(gè)啥?在講解這個(gè)之前你需要了解v-model在組件中的用法(你可以在Vue3官網(wǎng)查詢),以及他們的編譯結(jié)果。通過(guò)編譯結(jié)果我們可以發(fā)現(xiàn),新增了屬性modelModifiers,這就是咱們下面訪問(wèn)的modifiersKey了,同時(shí)v-model指令本質(zhì)上會(huì)被編譯為onUpdate:modelValue,所以我們可以通過(guò)判斷prop的開頭是否是onUpdate來(lái)判斷這是不是一個(gè)v-model指令。有了這兩點(diǎn)我們就能理解第二部分的代碼在做什么了。首先判斷當(dāng)前發(fā)射的事件是否是v-model事件,如果是,獲取修飾符對(duì)傳遞的參數(shù)進(jìn)行轉(zhuǎn)換,然后再傳遞給發(fā)射的事件函數(shù)。
<template>
<Comp v-model.trim.number = "a" />
</template>
//編譯后
function render(_ctx, _cache) {
const _component_Comp = _resolveComponent("Comp", true)
return (_openBlock(), _createBlock(_component_Comp, {
modelValue: _ctx.a,
"onUpdate:modelValue": $event => ((_ctx.a) = $event),
modelModifiers: { trim: true,number:true }
}, null, 8, ["modelValue"]))
}
function emit(instance, event, ...rawArgs) {
//省略第一部分代碼...
let args = rawArgs;
//判斷是否是v-model事件 => update:modelValue
const isModelListener = event.startsWith("update:");
const modelArg = isModelListener && event.slice(7); //modelValue
if (modelArg && modelArg in props) {
//獲取modifiersKey=>modelModifiers
const modifiersKey = `${
modelArg === "modelValue" ? "model" : modelArg
}Modifiers`;
//當(dāng)給組件傳遞v-model的時(shí)候
//<Button v-model.trim="a"></Button>
//當(dāng)這樣傳遞的時(shí)候會(huì)收到modelModifiers={trim:true}
const { number, trim } = props[modifiersKey] || shared.EMPTY_OBJ;
//對(duì)emit('',...args)傳遞給父組件的參數(shù)執(zhí)行trim()
if (trim) {
args = rawArgs.map((a) => a.trim());
}
if (number) {
args = rawArgs.map(shared.toNumber);
}
}
//省略第三部分代碼...
}
- 首先通過(guò)event在props中找到需要發(fā)射的事件,如果存在執(zhí)行即可。如果添加了
once修飾符,例如<C @close-Model.once="handler"></C>他實(shí)際接收到的屬性是onCloseModelOnce,而如果不寫once修飾符就收到的就是onCloseModel,所以添加了once修飾符的執(zhí)行一次后會(huì)被放入emitted當(dāng)中,以后在觸發(fā)就不會(huì)在執(zhí)行了。
function emit(instance, event, ...rawArgs) {
//省略第二部分的代碼...
//這里是簡(jiǎn)單寫法,源碼實(shí)際上對(duì)
//event這個(gè)字符串做了改造。
let handler = props[event]
//如果存在則執(zhí)行
if (handler) {
handler.apply(instance,args)
}
//如果有once事件,存入emitted,并執(zhí)行,以后不再執(zhí)行
const onceHandler = props[handlerName + `Once`];
if (onceHandler) {
if (!instance.emitted) {
instance.emitted = {};
} else if (instance.emitted[handlerName]) {
return;
}
instance.emitted[handlerName] = true;
handler.apply(instance,args);
}
}
- 總結(jié)一下
emit函數(shù)的作用: 如果聲明的emits中含有函數(shù),作為檢驗(yàn)函數(shù),檢驗(yàn)不通過(guò)警告用戶。然后對(duì)組件使用v-model指令做了處理,主要是對(duì)修飾符的實(shí)現(xiàn)。當(dāng)然在HTML標(biāo)簽中使用修飾符并不是在這里實(shí)現(xiàn)的,這里僅實(shí)現(xiàn)了組件的v-model修飾符。最后調(diào)用傳遞父組件傳遞給子組件的函數(shù),并且把子組件要傳遞的參數(shù)給了父組件。如果含有once修飾符,放入emitted緩存中,只執(zhí)行一次。
inheritAttrs:如果給組件傳遞了某些props屬性,但是沒(méi)有給組件聲明props、emits、或者事件監(jiān)聽(tīng)器、那么id屬性將會(huì)透?jìng)鞯?code>subTree節(jié)點(diǎn)上,例如:<Comp id= "com"></Comp>如果subTree是一個(gè)div節(jié)點(diǎn),那么id將會(huì)賦值到這個(gè)div上。當(dāng)然你可以設(shè)置inheritAttrs為false禁止透?jìng)鳌?/li>存放生命周期的屬性: 我們可以看到下面有一些簡(jiǎn)寫的屬性例如:bc、c、bm等,這些都是生命周期鉤子,在這里需要注意的是,這些屬性的值應(yīng)該是一個(gè)數(shù)組,因?yàn)槿绻脩羰褂昧?code>mixins或者extends這些屬性,那么同一個(gè)生命周期函數(shù)可能會(huì)包含多個(gè),而這些生命周期函數(shù)都應(yīng)該被調(diào)用,所以他們的值應(yīng)當(dāng)是一個(gè)數(shù)組例如:
export default {
mixins:[{beforeCreate(){}}]
beforeCreate(){}
}
//那么bc:[createBefore1,createBefore2]
- 還有一些
其他的屬性,我都放在了注釋當(dāng)中,因?yàn)榭磫卧~意思就能理解,就不額外講解了,當(dāng)然后續(xù)這些屬性都將會(huì)用到,也許現(xiàn)在看這些屬性還是比較片面的。但是通過(guò)后面的講解,你將會(huì)逐漸了解這些屬性的作用。
function createComponentInstance(vnode, parent, suspense) {
const type = vnode.type;
const appContext = (parent ? parent.appContext : vnode.appContext) || {};
const instance = {
uid: uid++, //當(dāng)前實(shí)例的id
vnode, //當(dāng)前實(shí)例對(duì)應(yīng)的vnode
type, //當(dāng)前實(shí)例對(duì)應(yīng)的編譯后的.vue生成的對(duì)象
parent, //當(dāng)前實(shí)例的父實(shí)例
appContext, //app的上下文包含全局注入的插件,自定義指令等
root: null, //當(dāng)前組件實(shí)例的根實(shí)例
//響應(yīng)式觸發(fā)的更新next為null,
//在更新的過(guò)程中父組件調(diào)用了子組件的
//instance.update會(huì)賦值next為最新組件vnode
next: null,
subTree: null, //調(diào)用render函數(shù)后的Vnode(處理了透?jìng)?
effect: null, //實(shí)例的ReactiveEffect
update: null, //副作用的scheduler
scope: new EffectScope(true),
//template編譯結(jié)果或setup返回值為函數(shù)
//或.vue文件寫的template編譯為的render函數(shù)
render: null, //渲染函數(shù)
proxy: null, //代理后的ctx
exposed: null, //調(diào)用了ctx.expose()方法(限制暴露的數(shù)據(jù))
exposeProxy: null, //調(diào)用了getExposeProxy方法后的expose
withProxy: null,
//當(dāng)前組件的provides,父實(shí)例有則讀取父實(shí)例的否則讀取app上的
//父組件的provides后續(xù)會(huì)掛載到prototype上,重新賦值當(dāng)前真實(shí)
//的provide上,這樣可以通過(guò)原型鏈訪問(wèn)到所有上代組件中的provide
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null,
renderCache: [],
components: null, //當(dāng)前組件的可用組件
directives: null, //當(dāng)前組件的自定義指令
//合并mixins和extends中的props屬性
propsOptions: normalizePropsOptions(type, appContext),
//合并mixins和extends中的emits屬性
emitsOptions: normalizeEmitsOptions(type, appContext),
emit: null, //當(dāng)前實(shí)例調(diào)用emit的函數(shù)
emitted: null, //含有once修飾符的,執(zhí)行一次后放入這里不再執(zhí)行
propsDefaults: {}, //默認(rèn)props
//是否透?jìng)鱝ttrs
inheritAttrs: type.inheritAttrs,
// state
ctx: {}, //當(dāng)前實(shí)例的上下文也就是this
data: {}, //data函數(shù)返回的值,被代理后才放入
props: {}, //接受到的組件屬性
attrs: {}, //接受到的標(biāo)簽屬性
slots: {}, //組件傳遞的插槽內(nèi)容
refs: {}, //存入的refs
setupState: {}, //setup的返回值
//expose attrs slots emit
setupContext: null, //傳遞給setup的ctx(只有四個(gè)屬性)
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null, //setup使用了async修飾符 返回的promise保存在這里
asyncResolved: false,
isMounted: false, //是否掛載
isUnmounted: false, //是否卸載
isDeactivated: false,
bc: null, //beforeCreate
c: null, //create
bm: null, //beforeMount
m: null, //mount
bu: null, //beforeUpdate
u: null, //update
um: null, //unmount
bum: null, //beforeUnmount
//若組件實(shí)例是 <KeepAlive> 緩存樹的一部分,
//當(dāng)組件從 DOM 中被移除時(shí)調(diào)用。deactivated
da: null,
//若組件實(shí)例是 <KeepAlive> 緩存樹的一部分,
//當(dāng)組件被插入到 DOM 中時(shí)調(diào)用。activated
a: null,
//在一個(gè)響應(yīng)式依賴被組件觸發(fā)了重新渲染之后調(diào)用。
//renderTriggered
rtg: null,
//在一個(gè)響應(yīng)式依賴被組件的渲染作用追蹤后調(diào)用。
//renderTracked
rtc: null,
/**
* 錯(cuò)誤捕獲鉤子
* 組件渲染
* 事件處理器
* 生命周期鉤子
* setup() 函數(shù)
* 偵聽(tīng)器
* 自定義指令鉤子
* 過(guò)渡鉤子
* 錯(cuò)誤捕獲鉤子
*/
ec: null, //errorHandler
sp: null, //serverPrefetch
};
//創(chuàng)建實(shí)例的上下文
instance.ctx = createDevRenderContext(instance);
//當(dāng)前實(shí)例的根實(shí)例
instance.root = parent ? parent.root : instance;
instance.emit = emit.bind(null, instance);
if (vnode.ce) {
vnode.ce(instance);
}
return instance;
}
總結(jié)
- 掛載組件一共執(zhí)行了三個(gè)主要的函數(shù):
createInstanceComponent(創(chuàng)建組件實(shí)例)、setupComponent(初始化組件)、setupRenderEffect(設(shè)置更新副作用)。 - 本文我們重點(diǎn)講解了創(chuàng)建組件實(shí)例,以及對(duì)幾乎所有的屬性進(jìn)行了詳細(xì)的講解,當(dāng)然
suspense相關(guān)屬性我們會(huì)在講解suspense的時(shí)候詳細(xì)為大家剖析。我們還重點(diǎn)講解了ctx對(duì)象,其實(shí)觀察編譯結(jié)果,所有我們?cè)?code>template中訪問(wèn)的xxx變量都會(huì)變成ctx.xxx,本質(zhì)上就是將所有的可能用到的變量都代理到了ctx上,代理的方式就是Object.defineProperty。當(dāng)然還有如何標(biāo)準(zhǔn)化propsOptions、emitsOptions、同時(shí)還詳細(xì)講解了組件的emit如何實(shí)現(xiàn)。以及在組件中使用v-model和它的修飾符trim、number的實(shí)現(xiàn)過(guò)程,once修飾符的實(shí)現(xiàn)原理。 - 下一小節(jié)我們將繼續(xù)剖析組件掛載的
第二個(gè)流程-初始化組件。
以上就是Vue3組件掛載之創(chuàng)建組件實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue3 組件掛載創(chuàng)建實(shí)例的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue項(xiàng)目啟動(dòng)后沒(méi)有局域網(wǎng)地址問(wèn)題
這篇文章主要介紹了vue項(xiàng)目啟動(dòng)后沒(méi)有局域網(wǎng)地址問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
vue跳轉(zhuǎn)外部鏈接始終有l(wèi)ocalhost的問(wèn)題
這篇文章主要介紹了vue跳轉(zhuǎn)外部鏈接始終有l(wèi)ocalhost的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Vue實(shí)現(xiàn)boradcast和dispatch的示例
這篇文章主要介紹了Vue實(shí)現(xiàn)boradcast和dispatch的示例,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2020-11-11
vue路由緩存的幾種實(shí)現(xiàn)方式小結(jié)
這篇文章主要介紹了vue路由緩存的幾種實(shí)現(xiàn)方式,結(jié)合實(shí)例形式詳細(xì)分析了vue.js路由緩存常見(jiàn)實(shí)現(xiàn)方式、使用技巧與操作注意事項(xiàng),需要的朋友可以參考下2020-02-02
部署Vue項(xiàng)目到服務(wù)器后404錯(cuò)誤的原因及解決方案
文章介紹了Vue項(xiàng)目部署步驟以及404錯(cuò)誤的解決方案,部署步驟包括構(gòu)建項(xiàng)目、上傳文件、配置Web服務(wù)器、重啟Nginx和訪問(wèn)域名,404錯(cuò)誤通常是由于歷史模式問(wèn)題導(dǎo)致的,解決方法是修改Nginx配置,將所有頁(yè)面請(qǐng)求重定向到index.html,并在Vue應(yīng)用中覆蓋所有路由情況2025-02-02
關(guān)于this.$refs獲取不到dom的可能原因及解決方法
這篇文章主要介紹了關(guān)于this.$refs獲取不到dom的可能原因及解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11

