Vue3源碼分析組件掛載初始化props與slots
前情提要
- 上文我們分析了掛載組件主要調(diào)用了三個(gè)函數(shù): createComponentInstance(創(chuàng)建組件實(shí)例)、setupComponent(初始化組件)、setupRenderEffect(更新副作用)。并且上一節(jié)中我們已經(jīng)詳細(xì)講解了組件實(shí)例上的所有屬性,還包括emit、provide等的實(shí)現(xiàn)。本文我們將繼續(xù)介紹組件掛載流程中的初始化組件。
本文主要內(nèi)容
- 初始化props和slots的主要流程。
- 如何將傳遞給組件的屬性分發(fā)給
props和attrs(需要被透?jìng)鞯膶傩?。 - 用戶自己實(shí)現(xiàn)了
render函數(shù),如何對(duì)其進(jìn)行標(biāo)準(zhǔn)化。 - 標(biāo)準(zhǔn)的插槽需要滿足哪些條件。
初始化組件
(1).setupComponent
setupComponent: 這個(gè)函數(shù)主要用于初始化組件。內(nèi)部主要調(diào)用了initProps、initSlot、對(duì)于有狀態(tài)組件還需要調(diào)用setupStatefulComponent。
function setupComponent(instance) {
//獲取vnode的props(真正傳遞的props)
const { props, children } = instance.vnode;
//判斷當(dāng)前是否是有狀態(tài)組件組件
const isStateful = isStatefulComponent(instance);
//通過(guò)傳遞的真實(shí)props和聲明的props 分離組件參數(shù)
//組件參數(shù)放入props中 其余放入instance.attrs
//處理了props的default情況等
initProps(instance, props, isStateful);
//初始化插槽
initSlots(instance, children);
//驗(yàn)證名稱是否合法,components中的組件名稱是否
//合法,代理instance.ctx,創(chuàng)建setup函數(shù)的ctx,調(diào)用setup函數(shù)
//處理得到的結(jié)果
const setupResult = isStateful ?
setupStatefulComponent(instance) : undefined;
return setupResult;
}
isStatefulComponent: 這個(gè)主要用于判斷是否是有狀態(tài)組件、還記得Vue3源碼分析(4)中提到的ShapeFlag嗎?我們?cè)?code>createVNode中會(huì)判斷type的類型、然后設(shè)置shapeFlag來(lái)標(biāo)識(shí)當(dāng)前創(chuàng)建的虛擬節(jié)點(diǎn)類型。因此我們只需要獲取組件的vNode、而vNode中有shapeFlag然后判斷他的值,就知道他是不是有狀態(tài)組件了。
function isStatefulComponent(instance) {
return instance.vnode.shapeFlag &
ShapeFlags.STATEFUL_COMPONENT;
}
(2).initProps
initProps: 在創(chuàng)建組件實(shí)例中,我們只對(duì)propsOptions做了處理、但是props和attrs目前都還是null、所以我們需要區(qū)分出來(lái)那些是props那些是attrs,同時(shí)有些propsOptions中設(shè)置了default屬性,那么我們還需要判斷是否傳遞了這個(gè)屬性,如果沒(méi)有傳遞那么應(yīng)該用default屬性中的值、又比如傳遞了 <Comp yes></Comp>并且聲明了props:{yes:Boolean},那么應(yīng)該將yes的值變?yōu)?code>true。而這些就是在初始化props的時(shí)候完成的。
function initProps(instance, rawProps, isStateful) {
//定義需要放入的
const props = {};
const attrs = {};
//attrs.__vInternal = 1
shared.def(attrs, InternalObjectKey, 1);
//創(chuàng)建propsDefaults
instance.propsDefaults = Object.create(null);
//將真實(shí)傳遞的props分配給instance的props和attrs
setFullProps(instance, rawProps, props, attrs);
//遍歷normalized(合并和的props)
for (const key in instance.propsOptions[0]) {
if (!(key in props)) {
props[key] = undefined;
}
}
//最后將分配好的props和attrs賦值到instance
if (isStateful) {
instance.props = reactivity.shallowReactive(props);
} else {
//不存在type.props則讓props為attrs
if (!instance.type.props) {
instance.props = attrs;
} else {
instance.props = props;
}
}
instance.attrs = attrs;
}
setFullProps: 在Vue3源碼分析(5)中我們?cè)敿?xì)講解了propsOptions,如果讀到這里還是不理解的小伙伴可以跳到上一章再去看看。首先重propsOptions中解構(gòu)到options和needCastKeys(需要特殊處理的key)。options就是進(jìn)行標(biāo)準(zhǔn)化后的組件定義的props。
- 遍歷真正傳遞給組件的
props,拿到key去options中尋找,如果找到了,表示這個(gè)屬性是組件需要接受的props,進(jìn)一步判斷是否是需要特殊處理的key如果不是就可以放入props中。 - 如果是需要特殊處理的key,獲取他的值放入
rawCastValues當(dāng)中。如果在options中沒(méi)有找到,就判斷一下emitsOptions中是否有,如果這里面也沒(méi)有那就可以放入attrs中,attrs就是需要透?jìng)鞯?code>subTree上的屬性。 - 最后遍歷需要特殊處理的
key調(diào)用resolvePropValue對(duì)props進(jìn)行最后的處理。
function setFullProps(instance, rawProps, props, attrs) {
//獲取通過(guò)mixins和extends合并的props
const [options, needCastKeys] = instance.propsOptions;
let hasAttrsChanged = false; //attrs是否發(fā)生改變
let rawCastValues;
if (rawProps) {
for (let key in rawProps) {
//如果key是"ref" "key" "ref_for" "ref_key"
//"onVnodeBeforeMount" "onVnodeMounted"
//"onVnodeBeforeUpdate "onVnodeUpdated"
//"onVnodeBeforeUnmount" "onVnodeUnmounted"
//那么就跳過(guò)
if (shared.isReservedProp(key)) {
continue;
}
//獲取rawProps:{a:1}=>value=1
const value = rawProps[key];
let camelKey; //小駝峰式的key
if (
options &&
shared.hasOwn(options, (camelKey = shared.camelize(key)))
) {
//這個(gè)key不是含有default屬性的
if (!needCastKeys || !needCastKeys.includes(camelKey)) {
props[camelKey] = value;
}
//props:{"msg":{default:"a"}}
//含有default屬性的放入rawCastValues中
else {
(rawCastValues || (rawCastValues = {}))[camelKey] = value;
}
}
//判斷當(dāng)前的key是否是用于emits的
else if (!isEmitListener(instance.emitsOptions, key)) {
//不是emit自定義事件的key也不是組件參數(shù)那么就是attrs
if (!(key in attrs) || value !== attrs[key]) {
attrs[key] = value;
hasAttrsChanged = true;
}
}
}
}
/**
*
* 這里涉及到四個(gè)屬性instance, rawProps, props, attrs
* instance:是當(dāng)前組件的實(shí)例
* rawProps:真正傳遞的props可能含有組件參數(shù)props,
* 標(biāo)簽屬性attrs,自定義emit事件
* props:代表聲明并且接受到的props
* attrs:代表沒(méi)有聲明props也不屬于emits屬性的屬性
* needCastKeys:代表需要特殊處理的屬性
* 例如props:{msg:{default:"a"}}那么msg會(huì)被放入
* needCastKeys中
*
*/
if (needCastKeys) {
//獲取非響應(yīng)式的props
const rawCurrentProps = reactivity.toRaw(props);
const castValues = rawCastValues || {};
for (let i = 0; i < needCastKeys.length; i++) {
const key = needCastKeys[i]; //msg
//對(duì)于有default的屬性進(jìn)行重設(shè)
//props:{msg:{default:"a"}}
props[key] = resolvePropValue(
options, //合并mixins和extends后的props(定義方)
rawCurrentProps, //非響應(yīng)式的props(接受方)
key, //(含有default)的key "msg"
//例如傳遞了"msg":1 定義了:props:{msg:{default:"a"}}
//castValues[key]=1
castValues[key],
instance, //實(shí)例
!shared.hasOwn(castValues, key)
);
}
}
return hasAttrsChanged;
}
resolvePropValue: 對(duì)特殊的key進(jìn)行處理。
- 首先從
opt中判斷是否有default屬性,如果有default屬性而且傳遞的value是undefined的話表示需要使用默認(rèn)值,還需要進(jìn)一步判斷,如果傳遞的不是函數(shù)但是聲明的是函數(shù),需要將value設(shè)置為這個(gè)函數(shù)的返回值。例如:props:{yes:Number,default:(props)=>{}}并且沒(méi)有向組件傳遞yes這個(gè)參數(shù),那么yes的值將會(huì)是default函數(shù)的返回值。 - 對(duì)于
propsOptions中定義的接受值類型是Boolean的,但是又沒(méi)有傳遞且沒(méi)有默認(rèn)值則設(shè)置這個(gè)值為false。 - 當(dāng)然還有<Comp yes></Comp>并且聲明了是
Boolean,則會(huì)設(shè)置為true。
function resolvePropValue(options, props, key, value, instance, isAbsent) {
//獲取{msg:{default:"a"}}中的{default:"a"}
const opt = options[key];
if (opt != null) {
//判斷是否有default屬性
const hasDefault = shared.hasOwn(opt, "default");
//如果定義了default但是沒(méi)有接受到value值
if (hasDefault && value === undefined) {
const defaultValue = opt.default;
//如果需要接受的類型不是函數(shù),但是接受到了函數(shù)
//看看實(shí)例的propsDefaults是否有當(dāng)前key的值
//還是沒(méi)有則調(diào)用這個(gè)defaultValue函數(shù)取得值
if (opt.type !== Function && shared.isFunction(defaultValue)) {
const { propsDefaults } = instance;
if (key in propsDefaults) {
value = propsDefaults[key];
} else {
//包裹是為了在調(diào)用這個(gè)函數(shù)的時(shí)候
//獲取當(dāng)前實(shí)例不會(huì)出錯(cuò)
setCurrentInstance(instance);
value = propsDefaults[key] = defaultValue.call(null, props);
unsetCurrentInstance();
}
}
//設(shè)置為默認(rèn)值
else {
value = defaultValue;
}
}
//需要接受的類型是Boolean
if (opt[0]) {
//沒(méi)有設(shè)置默認(rèn)值,也沒(méi)有傳遞這個(gè)值則為false
if (isAbsent && !hasDefault) {
value = false;
}
//<Comp yes></Comp>并且聲明了yes則設(shè)置為true
else if (opt[1] && value === "") {
value = true;
}
}
}
return value;
}
(3).initSlots
initSlots:還記得在Vue3源碼分析(4)中我們?cè)敿?xì)講解了normalizeChildren,他主要用于標(biāo)準(zhǔn)化插槽,給vNode的shapeFlag加上ARRAY_CHILDREN或TEXT_CHILDREN或SLOTS_CHILDREN的標(biāo)識(shí),但是并沒(méi)有添加到實(shí)例的slots屬性上。因?yàn)槟莻€(gè)時(shí)候還沒(méi)有創(chuàng)建實(shí)例,所以我們只能在那時(shí)候打上標(biāo)記,在創(chuàng)建實(shí)例之后,也就是現(xiàn)在,在去初始化slots。對(duì)于SLOTS_CHILDREN、TEXT_CHILDREN、ARRAY_CHILDREN分別是在那種情況下添加到shapeFlag上的,如果你不了解可能會(huì)影響這一段代碼的閱讀,建議在看看第四小節(jié)。因?yàn)殚g隔較遠(yuǎn),所以理解起來(lái)很困難,這部分的文章主要是闡述整個(gè)Vue3的運(yùn)行機(jī)制。我們后面的章節(jié)還會(huì)單獨(dú)講解slots的實(shí)現(xiàn)。
SLOTS_CHILDREN: 首先判斷children._是否存在,如果是通過(guò)Vue的編譯器得到的那么一定會(huì)有這個(gè)標(biāo)識(shí),當(dāng)然,用戶自己書寫render函數(shù)也可以自己傳遞這個(gè)標(biāo)識(shí)符。但是大部分用戶是不會(huì)傳遞的,所以else分支中就是為了處理這種情況,而對(duì)于children._存在的,可以直接把children當(dāng)做實(shí)例的slots屬性。_標(biāo)識(shí)有三個(gè)值STABLE、DYNAMIC、FORWORD這個(gè)在第四小節(jié)也已經(jīng)講過(guò)了,就不在重復(fù)了。TEXT_CHILDREN、ARRAY_CHILDREN: 因?yàn)?code>children不是一個(gè)對(duì)象,而是數(shù)組或字符串或null,那么需要將其標(biāo)準(zhǔn)化為對(duì)象形式。調(diào)用normalizeVNodeSlots處理。
function initSlots(instance, children) {
//判斷當(dāng)前實(shí)例的children是否是slots
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
const type = children._; //獲取shapeSlots
//有"_"標(biāo)識(shí)表示是通過(guò)compiler編譯得到的
if (type) {
//如果有SLOTS_CHILDREN標(biāo)識(shí) 表示children就是slots屬性
instance.slots = reactivity.toRaw(children);
//將_屬性變?yōu)椴豢擅杜e屬性
shared.def(children, "_", type);
} else {
/**
* render(){
* return h(Comp,null,{
* default:()=>h("div",null),
* header:()=>h("div",null)
* })
* }
* 沒(méi)有則表示用戶自己寫了render函數(shù)
* 這個(gè)時(shí)候用戶可能不會(huì)添加"_"屬性
* 所以需要對(duì)slots進(jìn)行標(biāo)準(zhǔn)化
*/
normalizeObjectSlots(children, (instance.slots = {}));
}
} else {
instance.slots = {};
//如果children為字符串或者null或數(shù)組情況
if (children) {
normalizeVNodeSlots(instance, children);
}
}
//標(biāo)識(shí)slots為內(nèi)部屬性
shared.def(instance.slots, InternalObjectKey, 1);
}
- 我們先來(lái)看看到底要標(biāo)準(zhǔn)化成什么樣子,其實(shí)對(duì)于
slots所有的標(biāo)準(zhǔn)化都是為了,將不標(biāo)準(zhǔn)的形式轉(zhuǎn)化為正常通過(guò)編譯得到的樣子。 - 我們主要關(guān)注
createBlock的第三個(gè)參數(shù)對(duì)象。通過(guò)觀察我們可以發(fā)現(xiàn)標(biāo)準(zhǔn)化的slots應(yīng)該滿足,
- 一個(gè)具名插槽對(duì)應(yīng)一個(gè)創(chuàng)建好的
VNode,我們這個(gè)例子只有default所以children對(duì)象中只有default; - 并且必須由
_withCtx包裹;(確保上下文,禁止block追蹤) - 參數(shù)必須是一個(gè)函數(shù),不能是數(shù)組;(提升性能)
- 函數(shù)的返回值必須是一個(gè)數(shù)組。(標(biāo)準(zhǔn)化)
- 如果你想自己書寫標(biāo)準(zhǔn)的插槽,你就應(yīng)當(dāng)滿足以上四個(gè)條件(我選擇模板編譯)。
<template>
<Comp>
我是插槽內(nèi)容
</Comp>
</template>
//編譯后
function render(_ctx, _cache) {
const _component_Comp = _resolveComponent("Comp", true)
return (_openBlock(),
_createBlock(_component_Comp, null, {
default: _withCtx(() => [
_createTextVNode(" 我是插槽內(nèi)容 ")
]),
_: 1 /* STABLE */
}))
}
normalizeObjectSlots: 改造成正常編譯后的樣子。因?yàn)闆](méi)有_標(biāo)識(shí),所以不是通過(guò)編譯得到的,這將不能作為標(biāo)準(zhǔn)形式的slots,將其標(biāo)準(zhǔn)化。
- 對(duì)于key以"_"開(kāi)頭或
key為$stable將不會(huì)進(jìn)行標(biāo)準(zhǔn)化。 - 判斷書寫的插槽模板是否是函數(shù),如果是則調(diào)用
noramlizeSlot,如果不是警告用戶,應(yīng)該書寫函數(shù)形式,同樣標(biāo)準(zhǔn)化插槽的value然后包裝成函數(shù)在返回。
const normalizeObjectSlots = (rawSlots, slots, instance) => {
const ctx = rawSlots._ctx;
for (const key in rawSlots) {
//_開(kāi)頭或者$stable跳過(guò)
//這將允許設(shè)置不進(jìn)行標(biāo)準(zhǔn)化的插槽
if (isInternalKey(key)) continue;
//獲取slots的值
const value = rawSlots[key];
//如果value已經(jīng)是一個(gè)函數(shù)了,需要包裹withCtx執(zhí)行
//進(jìn)行標(biāo)準(zhǔn)化 都需要改成通過(guò)編譯的樣子
if (shared.isFunction(value)) {
//給instance.slots賦值
slots[key] = normalizeSlot(key, value, ctx);
}
/**
* 用戶不寫函數(shù),拋出警告,使用函數(shù)的性能將會(huì)更好
* render(){
* return createVnode(Comp,null,{
* default:createVnode('div',null)
* })
* }
*/
else if (value != null) {
console.warn(
`Non-function value encountered for slot "${key}". ` +
`Prefer function slots for better performance.`
);
//經(jīng)過(guò)normalizeSlotValue處理 返回的createVnode一定通過(guò)數(shù)組包裹
const normalized = normalizeSlotValue(value);
slots[key] = () => normalized;
}
}
};
normalizeSlot:key代表的是插槽名稱(具名插槽,默認(rèn)為default),rawSlot代表返回虛擬節(jié)點(diǎn)的函數(shù)(rawSlot=()=>createVNode()),所以這個(gè)函數(shù)本質(zhì)上是調(diào)用normalizeSlotValue對(duì)虛擬節(jié)點(diǎn)進(jìn)行標(biāo)準(zhǔn)化,然后包裹_withCtx,最后返回經(jīng)過(guò)包裹的虛擬節(jié)點(diǎn)。接下來(lái)我們先看看withCtx執(zhí)行了什么。
const normalizeSlot = (key, rawSlot, ctx) => {
//已經(jīng)經(jīng)過(guò)標(biāo)準(zhǔn)化的slot不需要在進(jìn)行標(biāo)準(zhǔn)化
if (rawSlot._n) {
return rawSlot;
}
const normalized = withCtx((...args) => {
if (getCurrentInstance()) {
warn(
`Slot "${key}" invoked outside of the render function: ` +
`this will not track dependencies used in the slot. ` +
`Invoke the slot function inside the render function instead.`
);
}
//標(biāo)準(zhǔn)化插槽的值 rawSlot=> default:()=>createVnode('div',null)
return normalizeSlotValue(rawSlot(...args));
}, ctx);
//表示不是經(jīng)過(guò)compiler編譯的,是用戶自己寫的render函數(shù)
normalized._c = false;
return normalized;
};
withCtx: 將傳遞的fn包裹成renderFnWithContext在返回。
- 在執(zhí)行
fn的時(shí)候包裹一層currentRenderInstance,確保當(dāng)前的實(shí)例不出錯(cuò)。 renderFnWithContext有以下三個(gè)屬性:
_n:如果有這個(gè)屬性代表當(dāng)前函數(shù)已經(jīng)被包裹過(guò)了,不應(yīng)該被重復(fù)包裹。_c: 標(biāo)識(shí)的是當(dāng)前的插槽是通過(guò)編譯得到的,還是用戶自己寫的。_d: 表示執(zhí)行fn的時(shí)候是否需要禁止塊跟蹤,true代表禁止塊跟蹤,false代表允許塊跟蹤。
function withCtx(
fn,
ctx = getCurrentRenderingInstance(),
isNonScopedSlot
) {
if (!ctx) return fn;
if (fn._n) {
return fn;
}
//設(shè)置currentRenderingInstance,通過(guò)閉包確保調(diào)用fn的時(shí)候
//currentRenderingInstance實(shí)例為當(dāng)前實(shí)例
/**
* 如果用戶調(diào)用模板表達(dá)式內(nèi)的插槽
* <Button>
* <template>
* <slot></slot>
* </template>
* </Button>
* 可能會(huì)擾亂塊跟蹤,因此默認(rèn)情況下,禁止塊跟蹤,當(dāng)
* 調(diào)用已經(jīng)編譯的插槽時(shí)強(qiáng)制跳出(由.d標(biāo)志指示)。
* 如果渲染已編譯的slot則無(wú)需執(zhí)行此操作、因此
* 我們?cè)趓enderSlot中調(diào)用renderFnWithContext
* 時(shí),.d設(shè)置為false
*/
const renderFnWithContext = (...args) => {
//禁止塊追蹤,將isBlockTreeEnabled設(shè)置為0將會(huì)停止追蹤
if (renderFnWithContext._d) {
setBlockTracking(-1);
}
const prevInstance = setCurrentRenderingInstance(ctx);
const res = fn(...args);
setCurrentRenderingInstance(prevInstance);
//開(kāi)啟塊追蹤
if (renderFnWithContext._d) {
setBlockTracking(1);
}
return res;
};
//如果已經(jīng)是renderFnWithContext則不需要在包裝了
renderFnWithContext._n = true; //_n表示已經(jīng)經(jīng)過(guò)renderFnWithContext包裝
renderFnWithContext._c = true; //表示經(jīng)過(guò)compiler編譯得到
//true代表禁止塊追蹤,false代表開(kāi)啟塊追蹤
renderFnWithContext._d = true;
return renderFnWithContext;
}
normalizeSlotValue: 目前value傳遞的是單個(gè)VNode或者是數(shù)組類型的VNode,我們還需要對(duì)返回的所有VNode進(jìn)行標(biāo)準(zhǔn)化。這里主要是為了處理,比如default:()=>"asd",如果是字符串,他顯然可以這樣寫,但是我們需要將字符串變成patch階段能夠處理的VNode。
function normalizeSlotValue(value){
if(shared.isArray(value)){
return value.map(normalizeVNode)
}
return [normalizeVNode(value)]
}
normalizeVNode: 標(biāo)準(zhǔn)化虛擬節(jié)點(diǎn)。
- 當(dāng)前虛擬節(jié)點(diǎn)是
null、boolean,這樣的值不應(yīng)該顯示在頁(yè)面當(dāng)中,創(chuàng)建注釋節(jié)點(diǎn)。 - 當(dāng)前
虛擬節(jié)點(diǎn)是一個(gè)數(shù)組,需要由Fragment包裹。例如下面的寫法。
//自己寫render函數(shù)
export default {
render(){
return createVNode(Comp,null,{
default:()=>([
createVNode('div',null),
createVNode('div',null)
])
})
}
}
//如果是正常編譯獲得的那么應(yīng)該是
- 如果是
object,判斷當(dāng)前節(jié)點(diǎn)是否掛載過(guò),掛載過(guò)需要克隆節(jié)點(diǎn)再返回。例如下面這種情況:
export default{
render(){
return createVNode(Comp,null,{
default:()=>createTextVNode('123')
})
}
}
- 如果是
字符串或者number,創(chuàng)建文本節(jié)點(diǎn)即可。例如下面這種情況:
//自己寫render函數(shù)
export default {
render(){
return createVNode(Comp,null,{
default:()=>123
})
}
}
function normalizeVNode(child) {
if (child == null || typeof child === "boolean") {
//沒(méi)有child或者沒(méi)有實(shí)質(zhì)性內(nèi)容創(chuàng)建注釋節(jié)點(diǎn)
return createVNode(Comment);
} else if (shared.isArray(child)) {
//用戶直接寫了一個(gè)數(shù)組,需要包裹一層Fragment
return createVNode(Fragment, null, child.slice());
}
//如果這個(gè)節(jié)點(diǎn)已經(jīng)掛載過(guò)了克隆這個(gè)節(jié)點(diǎn)(復(fù)用節(jié)點(diǎn))
else if (typeof child === "object") {
return cloneIfMounted(child);
}
//string 或者 number
else {
return createVNode(Text, null, String(child));
}
}
- 到此為止我們就完成了對(duì)于對(duì)象形式的插槽標(biāo)準(zhǔn)化,并放到了實(shí)例的slots屬性上。 現(xiàn)在你可以通過(guò)訪問(wèn)
slots.default訪問(wèn)到經(jīng)過(guò)標(biāo)準(zhǔn)化后的虛擬節(jié)點(diǎn)了。而我們實(shí)際在項(xiàng)目中使用的是<slot name="default"></slot>,這個(gè)又是怎么渲染到頁(yè)面上的呢?大膽猜測(cè)一下就是根據(jù)name屬性獲取到key然后到instance.slots中去找到這個(gè)虛擬節(jié)點(diǎn)最后掛載到頁(yè)面就可以了。我們會(huì)在講解slots的實(shí)現(xiàn)章節(jié)詳細(xì)解釋,這里就不過(guò)多講解了。
render(){
return createVNode(Comp,null,{
default:createVNode('div')
})
}
//經(jīng)過(guò)標(biāo)準(zhǔn)化后,相當(dāng)于
render(){
return createVNode(Comp,null,{
default:withCtx(()=>[createVNode('div')])
})
}
//其他的情況都差不多,都是為了標(biāo)準(zhǔn)化為
//滿足上面四個(gè)條件的樣子
- 下面我們講解另一個(gè)分支,如果用戶用數(shù)組或字符串或數(shù)字作為
children參數(shù)呢?createVNode(Comp,null,[])就像這樣。又或者createVNode(Comp,null,123)這樣。這就是標(biāo)識(shí)為ARRAY_CHILDREN或TEXT_CHILDREN的情況了,顯然調(diào)用了normalizeVNodeSlots進(jìn)行處理。 normalizeVNodeSlots:這個(gè)情況我們可以把傳遞的第三個(gè)參數(shù)看成是調(diào)用對(duì)象形式的default函數(shù)的返回值,那么我們只需要標(biāo)準(zhǔn)化第三個(gè)參數(shù)然后包裝成一個(gè)函數(shù),賦值給slots.default就可以啦。
const normalizeVNodeSlots = (instance, children) => {
const normalized = normalizeSlotValue(children);
instance.slots.default = () => normalized;
};
額外內(nèi)容
- 在
normalizeVNode函數(shù)中,如果傳遞的child是一個(gè)對(duì)象,那么調(diào)用了cloneIfMounted,這個(gè)函數(shù)是干什么的呢?如果el有值,表示已經(jīng)有真實(shí)的DOM了,那么就一定調(diào)用了render函數(shù),也一定掛載過(guò)元素了。我們看看他是如何克隆節(jié)點(diǎn)的呢?
//掛載過(guò)的vnode有el屬性
function cloneIfMounted(child) {
return child.el === null || child.memo ?
child : cloneVNode(child);
}
cloneVNode: 用于淺克隆一個(gè)VNode。還可以提供額外的props合并之前VNode身上的屬性。
- 如果提供了
extraProps,調(diào)用mergeProps合并之前的props和新的props。對(duì)key為class、style的屬性做了特殊處理。并且后面的props可以覆蓋前面的props
- 當(dāng)
key為class的時(shí)候,之前的class已經(jīng)經(jīng)過(guò)標(biāo)準(zhǔn)化了一定是一個(gè)字符串,我們需要將新的class與之前的class合并為一個(gè)字符串。 - 當(dāng)
key為style的時(shí)候,合并新舊的style對(duì)象。 - 其余情況,讓新的覆蓋舊的。
function mergeProps(...args) {
const ret = {};
for (let i = 0; i < args.length; i++) {
const toMerge = args[i];
for (const key in toMerge) {
//結(jié)合class
if (key === "class") {
if (ret.class !== toMerge.class) {
ret.class = shared.normalizeClass([ret.class, toMerge.class]);
}
}
//結(jié)合style屬性
else if (key === "style") {
ret.style = shared.normalizeStyle([ret.style, toMerge.style]);
}
else if (key !== "") {
ret[key] = toMerge[key];
}
}
}
return ret;
}
- 將合并的新
props作為新的VNode的props屬性。如果傳遞了mergeRef參數(shù),表示需要合并ref,那么需要讀取mergeProps中的ref屬性進(jìn)行合并,之前的ref可能是數(shù)組(使用了v-for加ref),將最新的ref添加到數(shù)組的后面,不是數(shù)組則轉(zhuǎn)化為數(shù)組在合并他們兩個(gè)ref到這個(gè)數(shù)組中。 - 對(duì)于
靜態(tài)節(jié)點(diǎn),需要深度克隆children。
function cloneVNode(vnode, extraProps, mergeRef = false) {
const { props, ref, patchFlag, children } = vnode;
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;
const cloned = {
//省略了大量屬性,其他的屬性和傳遞的
//vnode一樣,這里只列舉了可能被改變的
key: mergedProps && normalizeKey(mergedProps),
ref:
extraProps && extraProps.ref
? mergeRef && ref
? shared.isArray(ref)
? ref.concat(normalizeRef(extraProps))
: [ref, normalizeRef(extraProps)]
: normalizeRef(extraProps)
: ref,
children:
patchFlag === PatchFlags.HOISTED && shared.isArray(children)
? children.map(deepCloneVNode)
: children,
shapeFlag: vnode.shapeFlag,
patchFlag:
extraProps && vnode.type !== Fragment
? patchFlag === PatchFlags.HOISTED
? PatchFlags.FULL_PROPS
: patchFlag | PatchFlags.FULL_PROPS
: patchFlag,
};
return cloned;
}
function deepCloneVNode(vnode) {
const cloned = cloneVNode(vnode);
if (shared.isArray(vnode.children)) {
cloned.children = vnode.children.map(deepCloneVNode);
}
return cloned;
}
總結(jié)
- 本文我們主要介紹了如何對(duì)生成的組件實(shí)例的props和slots屬性進(jìn)行初始化。
- 在初始化props中,根據(jù)定義組件的props和接受到的props放到
instance.props中,對(duì)于定義了但是沒(méi)有傳遞,又有默認(rèn)值的我們需要使用默認(rèn)值。當(dāng)然我們還需要設(shè)置透?jìng)鲗傩?code>attrs的值,如果傳遞了,但是沒(méi)有在props、emits中定義,那么會(huì)認(rèn)為是透?jìng)鲗傩?,需要將其放入?code>instance.attrs中。 - 然后我們?cè)敿?xì)講解了slots的初始化。這一部分主要是對(duì)用戶自己使用
render函數(shù)來(lái)渲染的模板,進(jìn)行標(biāo)準(zhǔn)化保證后續(xù)的執(zhí)行不會(huì)出錯(cuò)。 - 最后我們?cè)陬~外內(nèi)容中介紹了
cloneVNode的api實(shí)現(xiàn)。 - 下文中我們將會(huì)繼續(xù)講解,對(duì)于其他組件定義的屬性的初始化。也就是
setupStatefulComponent函數(shù),這里將會(huì)對(duì)watch、data、computed等屬性進(jìn)行處理,調(diào)用setup函數(shù)、beforeCreat,created鉤子等。
以上就是Vue3源碼分析組件掛載初始化props與slots的詳細(xì)內(nèi)容,更多關(guān)于Vue3組件掛載初始化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別
這篇文章主要介紹了Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
vue中的@click.native 原生點(diǎn)擊事件
這篇文章主要介紹了vue中的@click.native 原生點(diǎn)擊事件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue?websocket封裝實(shí)現(xiàn)方法詳解
項(xiàng)目中需要使用websocke,這個(gè)是我自己修修改改好多次之后用得最順手的一版,分享一下。這個(gè)封裝需要搭配vuex使用,因?yàn)槭盏降臄?shù)據(jù)都保存在vuex中了,真的絕絕子好用,解決了我一大堆問(wèn)題2022-09-09
vite+vue3搭建的工程實(shí)現(xiàn)批量導(dǎo)入store的module
這篇文章主要介紹了vite+vue3搭建的工程實(shí)現(xiàn)批量導(dǎo)入store的module方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vue3+ts封裝axios實(shí)例以及解決跨域問(wèn)題
在前端開(kāi)發(fā)中,使用axios進(jìn)行數(shù)據(jù)請(qǐng)求是常見(jiàn)的做法,封裝axios可以統(tǒng)一請(qǐng)求頭處理、方便接口管理、配置多攔截器等,提高代碼的可維護(hù)性和重用性,本文詳細(xì)記錄了axios的封裝過(guò)程,包括安裝、配置跨域處理、接口管理文件的創(chuàng)建等2024-09-09
利用vue和element-ui設(shè)置表格內(nèi)容分頁(yè)的實(shí)例
下面小編就為大家分享一篇利用vue和element-ui設(shè)置表格內(nèi)容分頁(yè)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03

