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)鞯膶傩?。 - 用戶(hù)自己實(shí)現(xiàn)了
render
函數(shù),如何對(duì)其進(jìn)行標(biāo)準(zhǔn)化。 - 標(biāo)準(zhǔn)的插槽需要滿(mǎ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)證名稱(chēng)是否合法,components中的組件名稱(chēng)是否 //合法,代理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
的類(lèi)型、然后設(shè)置shapeFlag
來(lái)標(biāo)識(shí)當(dāng)前創(chuàng)建的虛擬節(jié)點(diǎn)類(lèi)型。因此我們只需要獲取組件的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
的話(huà)表示需要使用默認(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
中定義的接受值類(lèi)型是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; //如果需要接受的類(lèi)型不是函數(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; } } //需要接受的類(lèi)型是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)然,用戶(hù)自己書(shū)寫(xiě)render
函數(shù)也可以自己傳遞這個(gè)標(biāo)識(shí)符。但是大部分用戶(hù)是不會(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)有則表示用戶(hù)自己寫(xiě)了render函數(shù) * 這個(gè)時(shí)候用戶(hù)可能不會(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)該滿(mǎn)足,
- 一個(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)化)
- 如果你想自己書(shū)寫(xiě)標(biāo)準(zhǔn)的插槽,你就應(yīng)當(dāng)滿(mǎn)足以上四個(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ū)寫(xiě)的插槽模板是否是函數(shù),如果是則調(diào)用
noramlizeSlot
,如果不是警告用戶(hù),應(yīng)該書(shū)寫(xiě)函數(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); } /** * 用戶(hù)不寫(xiě)函數(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
代表的是插槽名稱(chēng)(具名插槽,默認(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編譯的,是用戶(hù)自己寫(xiě)的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ò)編譯得到的,還是用戶(hù)自己寫(xiě)的。_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í)例 /** * 如果用戶(hù)調(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ù)組類(lèi)型的VNode
,我們還需要對(duì)返回的所有VNode
進(jìn)行標(biāo)準(zhǔn)化。這里主要是為了處理,比如default:()=>"asd",如果是字符串,他顯然可以這樣寫(xiě),但是我們需要將字符串變成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
包裹。例如下面的寫(xiě)法。
//自己寫(xiě)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)即可。例如下面這種情況:
//自己寫(xiě)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ì)性?xún)?nèi)容創(chuàng)建注釋節(jié)點(diǎn) return createVNode(Comment); } else if (shared.isArray(child)) { //用戶(hù)直接寫(xiě)了一個(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ò)訪(fǎng)問(wèn)
slots.default
訪(fǎng)問(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ì)解釋?zhuān)@里就不過(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)化為 //滿(mǎn)足上面四個(gè)條件的樣子
- 下面我們講解另一個(gè)分支,如果用戶(hù)用數(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ì)用戶(hù)自己使用
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-11vue中的@click.native 原生點(diǎn)擊事件
這篇文章主要介紹了vue中的@click.native 原生點(diǎn)擊事件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04Vue?websocket封裝實(shí)現(xiàn)方法詳解
項(xiàng)目中需要使用websocke,這個(gè)是我自己修修改改好多次之后用得最順手的一版,分享一下。這個(gè)封裝需要搭配vuex使用,因?yàn)槭盏降臄?shù)據(jù)都保存在vuex中了,真的絕絕子好用,解決了我一大堆問(wèn)題2022-09-09vite+vue3搭建的工程實(shí)現(xiàn)批量導(dǎo)入store的module
這篇文章主要介紹了vite+vue3搭建的工程實(shí)現(xiàn)批量導(dǎo)入store的module方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03vue3+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