Vue3源碼分析組件掛載初始化props與slots
更新時間:2022年10月21日 15:26:08 作者:豬豬愛前端
這篇文章主要為大家介紹了Vue3源碼分析組件掛載初始化props與slots實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
前情提要
- 上文我們分析了掛載組件主要調(diào)用了三個函數(shù): createComponentInstance(創(chuàng)建組件實例)、setupComponent(初始化組件)、setupRenderEffect(更新副作用)。并且上一節(jié)中我們已經(jīng)詳細講解了組件實例上的所有屬性,還包括emit、provide等的實現(xiàn)。本文我們將繼續(xù)介紹組件掛載流程中的初始化組件。
本文主要內(nèi)容
- 初始化props和slots的主要流程。
- 如何將傳遞給組件的屬性分發(fā)給
props
和attrs(需要被透傳的屬性)。 - 用戶自己實現(xiàn)了
render
函數(shù),如何對其進行標(biāo)準(zhǔn)化。 - 標(biāo)準(zhǔn)的插槽需要滿足哪些條件。
初始化組件
(1).setupComponent
setupComponent
: 這個函數(shù)主要用于初始化組件。內(nèi)部主要調(diào)用了initProps、initSlot
、對于有狀態(tài)組件還需要調(diào)用setupStatefulComponent
。
function setupComponent(instance) { //獲取vnode的props(真正傳遞的props) const { props, children } = instance.vnode; //判斷當(dāng)前是否是有狀態(tài)組件組件 const isStateful = isStatefulComponent(instance); //通過傳遞的真實props和聲明的props 分離組件參數(shù) //組件參數(shù)放入props中 其余放入instance.attrs //處理了props的default情況等 initProps(instance, props, isStateful); //初始化插槽 initSlots(instance, children); //驗證名稱是否合法,components中的組件名稱是否 //合法,代理instance.ctx,創(chuàng)建setup函數(shù)的ctx,調(diào)用setup函數(shù) //處理得到的結(jié)果 const setupResult = isStateful ? setupStatefulComponent(instance) : undefined; return setupResult; }
isStatefulComponent
: 這個主要用于判斷是否是有狀態(tài)組件、還記得Vue3源碼分析(4)中提到的ShapeFlag
嗎?我們在createVNode
中會判斷type
的類型、然后設(shè)置shapeFlag
來標(biāo)識當(dāng)前創(chuàng)建的虛擬節(jié)點類型。因此我們只需要獲取組件的vNode、而vNode
中有shapeFlag
然后判斷他的值,就知道他是不是有狀態(tài)組件了。
function isStatefulComponent(instance) { return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT; }
(2).initProps
initProps
: 在創(chuàng)建組件實例中,我們只對propsOptions
做了處理、但是props
和attrs
目前都還是null
、所以我們需要區(qū)分出來那些是props
那些是attrs
,同時有些propsOptions
中設(shè)置了default
屬性,那么我們還需要判斷是否傳遞了這個屬性,如果沒有傳遞那么應(yīng)該用default
屬性中的值、又比如傳遞了 <Comp yes></Comp>并且聲明了props:{yes:Boolean},那么應(yīng)該將yes
的值變?yōu)?code>true。而這些就是在初始化props的時候完成
的。
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); //將真實傳遞的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)中我們詳細講解了propsOptions
,如果讀到這里還是不理解的小伙伴可以跳到上一章再去看看。首先重propsOptions
中解構(gòu)到options
和needCastKeys(需要特殊處理的key)。options
就是進行標(biāo)準(zhǔn)化后的組件定義的props
。
- 遍歷真正傳遞給組件的
props
,拿到key
去options
中尋找,如果找到了,表示這個屬性是組件需要接受的props
,進一步判斷是否是需要特殊處理的key
如果不是就可以放入props
中。 - 如果是需要特殊處理的key,獲取他的值放入
rawCastValues
當(dāng)中。如果在options
中沒有找到,就判斷一下emitsOptions
中是否有,如果這里面也沒有那就可以放入attrs
中,attrs
就是需要透傳到subTree
上的屬性。 - 最后遍歷需要特殊處理的
key
調(diào)用resolvePropValue
對props
進行最后的處理。
function setFullProps(instance, rawProps, props, attrs) { //獲取通過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" //那么就跳過 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))) ) { //這個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; } } } } /** * * 這里涉及到四個屬性instance, rawProps, props, attrs * instance:是當(dāng)前組件的實例 * rawProps:真正傳遞的props可能含有組件參數(shù)props, * 標(biāo)簽屬性attrs,自定義emit事件 * props:代表聲明并且接受到的props * attrs:代表沒有聲明props也不屬于emits屬性的屬性 * needCastKeys:代表需要特殊處理的屬性 * 例如props:{msg:{default:"a"}}那么msg會被放入 * 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 //對于有default的屬性進行重設(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, //實例 !shared.hasOwn(castValues, key) ); } } return hasAttrsChanged; }
resolvePropValue
: 對特殊的key進行處理。
- 首先從
opt
中判斷是否有default
屬性,如果有default屬性而且傳遞的value
是undefined
的話表示需要使用默認(rèn)值,還需要進一步判斷,如果傳遞的不是函數(shù)但是聲明的是函數(shù),需要將value
設(shè)置為這個函數(shù)的返回值。例如:props:{yes:Number,default:(props)=>{}}并且沒有向組件傳遞yes
這個參數(shù),那么yes
的值將會是default函數(shù)的返回值。 - 對于
propsOptions
中定義的接受值類型是Boolean
的,但是又沒有傳遞且沒有默認(rèn)值則設(shè)置這個值為false
。 - 當(dāng)然還有<Comp yes></Comp>并且聲明了是
Boolean
,則會設(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但是沒有接受到value值 if (hasDefault && value === undefined) { const defaultValue = opt.default; //如果需要接受的類型不是函數(shù),但是接受到了函數(shù) //看看實例的propsDefaults是否有當(dāng)前key的值 //還是沒有則調(diào)用這個defaultValue函數(shù)取得值 if (opt.type !== Function && shared.isFunction(defaultValue)) { const { propsDefaults } = instance; if (key in propsDefaults) { value = propsDefaults[key]; } else { //包裹是為了在調(diào)用這個函數(shù)的時候 //獲取當(dāng)前實例不會出錯 setCurrentInstance(instance); value = propsDefaults[key] = defaultValue.call(null, props); unsetCurrentInstance(); } } //設(shè)置為默認(rèn)值 else { value = defaultValue; } } //需要接受的類型是Boolean if (opt[0]) { //沒有設(shè)置默認(rèn)值,也沒有傳遞這個值則為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)中我們詳細講解了normalizeChildren
,他主要用于標(biāo)準(zhǔn)化插槽,給vNode
的shapeFlag
加上ARRAY_CHILDREN
或TEXT_CHILDREN
或SLOTS_CHILDREN
的標(biāo)識,但是并沒有添加到實例的slots
屬性上。因為那個時候還沒有創(chuàng)建實例,所以我們只能在那時候打上標(biāo)記,在創(chuàng)建實例之后,也就是現(xiàn)在,在去初始化slots
。對于SLOTS_CHILDREN、TEXT_CHILDREN、ARRAY_CHILDREN
分別是在那種情況下添加到shapeFlag
上的,如果你不了解可能會影響這一段代碼的閱讀,建議在看看第四小節(jié)。因為間隔較遠,所以理解起來很困難,這部分的文章主要是闡述整個Vue3
的運行機制。我們后面的章節(jié)還會單獨講解slots
的實現(xiàn)。
SLOTS_CHILDREN
: 首先判斷children._
是否存在,如果是通過Vue的編譯器得到的那么一定會有這個標(biāo)識,當(dāng)然,用戶自己書寫render
函數(shù)也可以自己傳遞這個標(biāo)識符。但是大部分用戶是不會傳遞的,所以else分支中就是為了處理這種情況,而對于children._
存在的,可以直接把children
當(dāng)做實例的slots屬性。_
標(biāo)識有三個值STABLE、DYNAMIC、FORWORD這個在第四小節(jié)也已經(jīng)講過了,就不在重復(fù)了。TEXT_CHILDREN、ARRAY_CHILDREN
: 因為children
不是一個對象,而是數(shù)組或字符串或null
,那么需要將其標(biāo)準(zhǔn)化為對象形式。調(diào)用normalizeVNodeSlots
處理。
function initSlots(instance, children) { //判斷當(dāng)前實例的children是否是slots if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { const type = children._; //獲取shapeSlots //有"_"標(biāo)識表示是通過compiler編譯得到的 if (type) { //如果有SLOTS_CHILDREN標(biāo)識 表示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) * }) * } * 沒有則表示用戶自己寫了render函數(shù) * 這個時候用戶可能不會添加"_"屬性 * 所以需要對slots進行標(biāo)準(zhǔn)化 */ normalizeObjectSlots(children, (instance.slots = {})); } } else { instance.slots = {}; //如果children為字符串或者null或數(shù)組情況 if (children) { normalizeVNodeSlots(instance, children); } } //標(biāo)識slots為內(nèi)部屬性 shared.def(instance.slots, InternalObjectKey, 1); }
- 我們先來看看到底要標(biāo)準(zhǔn)化成什么樣子,其實對于
slots
所有的標(biāo)準(zhǔn)化都是為了,將不標(biāo)準(zhǔn)的形式轉(zhuǎn)化為正常通過編譯得到的樣子。 - 我們主要關(guān)注
createBlock
的第三個參數(shù)對象。通過觀察我們可以發(fā)現(xiàn)標(biāo)準(zhǔn)化的slots
應(yīng)該滿足,
- 一個具名插槽對應(yīng)一個創(chuàng)建好的
VNode
,我們這個例子只有default
所以children
對象中只有default
; - 并且必須由
_withCtx
包裹;(確保上下文,禁止block追蹤) - 參數(shù)必須是一個函數(shù),不能是數(shù)組;(提升性能)
- 函數(shù)的返回值必須是一個數(shù)組。(標(biāo)準(zhǔn)化)
- 如果你想自己書寫標(biāo)準(zhǔn)的插槽,你就應(yīng)當(dāng)滿足以上四個條件(我選擇模板編譯)。
<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
: 改造成正常編譯后的樣子。因為沒有_
標(biāo)識,所以不是通過編譯得到的,這將不能作為標(biāo)準(zhǔn)形式的slots
,將其標(biāo)準(zhǔn)化。
- 對于key以"_"開頭或
key為$stable
將不會進行標(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) { //_開頭或者$stable跳過 //這將允許設(shè)置不進行標(biāo)準(zhǔn)化的插槽 if (isInternalKey(key)) continue; //獲取slots的值 const value = rawSlots[key]; //如果value已經(jīng)是一個函數(shù)了,需要包裹withCtx執(zhí)行 //進行標(biāo)準(zhǔn)化 都需要改成通過編譯的樣子 if (shared.isFunction(value)) { //給instance.slots賦值 slots[key] = normalizeSlot(key, value, ctx); } /** * 用戶不寫函數(shù),拋出警告,使用函數(shù)的性能將會更好 * 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)過normalizeSlotValue處理 返回的createVnode一定通過數(shù)組包裹 const normalized = normalizeSlotValue(value); slots[key] = () => normalized; } } };
normalizeSlot
:key
代表的是插槽名稱(具名插槽,默認(rèn)為default),rawSlot
代表返回虛擬節(jié)點的函數(shù)(rawSlot=()=>createVNode()),所以這個函數(shù)本質(zhì)上是調(diào)用normalizeSlotValue
對虛擬節(jié)點進行標(biāo)準(zhǔn)化,然后包裹_withCtx,最后返回經(jīng)過包裹的虛擬節(jié)點。接下來我們先看看withCtx
執(zhí)行了什么。
const normalizeSlot = (key, rawSlot, ctx) => { //已經(jīng)經(jīng)過標(biāo)準(zhǔn)化的slot不需要在進行標(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)過compiler編譯的,是用戶自己寫的render函數(shù) normalized._c = false; return normalized; };
withCtx
: 將傳遞的fn
包裹成renderFnWithContext
在返回。
- 在執(zhí)行
fn
的時候包裹一層currentRenderInstance
,確保當(dāng)前的實例不出錯。 renderFnWithContext
有以下三個屬性:
_n
:如果有這個屬性代表當(dāng)前函數(shù)已經(jīng)被包裹過了,不應(yīng)該被重復(fù)包裹。_c
: 標(biāo)識的是當(dāng)前的插槽是通過編譯得到的,還是用戶自己寫的。_d
: 表示執(zhí)行fn
的時候是否需要禁止塊跟蹤,true
代表禁止塊跟蹤,false
代表允許塊跟蹤。
function withCtx( fn, ctx = getCurrentRenderingInstance(), isNonScopedSlot ) { if (!ctx) return fn; if (fn._n) { return fn; } //設(shè)置currentRenderingInstance,通過閉包確保調(diào)用fn的時候 //currentRenderingInstance實例為當(dāng)前實例 /** * 如果用戶調(diào)用模板表達式內(nèi)的插槽 * <Button> * <template> * <slot></slot> * </template> * </Button> * 可能會擾亂塊跟蹤,因此默認(rèn)情況下,禁止塊跟蹤,當(dāng) * 調(diào)用已經(jīng)編譯的插槽時強制跳出(由.d標(biāo)志指示)。 * 如果渲染已編譯的slot則無需執(zhí)行此操作、因此 * 我們在renderSlot中調(diào)用renderFnWithContext * 時,.d設(shè)置為false */ const renderFnWithContext = (...args) => { //禁止塊追蹤,將isBlockTreeEnabled設(shè)置為0將會停止追蹤 if (renderFnWithContext._d) { setBlockTracking(-1); } const prevInstance = setCurrentRenderingInstance(ctx); const res = fn(...args); setCurrentRenderingInstance(prevInstance); //開啟塊追蹤 if (renderFnWithContext._d) { setBlockTracking(1); } return res; }; //如果已經(jīng)是renderFnWithContext則不需要在包裝了 renderFnWithContext._n = true; //_n表示已經(jīng)經(jīng)過renderFnWithContext包裝 renderFnWithContext._c = true; //表示經(jīng)過compiler編譯得到 //true代表禁止塊追蹤,false代表開啟塊追蹤 renderFnWithContext._d = true; return renderFnWithContext; }
normalizeSlotValue
: 目前value
傳遞的是單個VNode或者是數(shù)組類型的VNode
,我們還需要對返回的所有VNode
進行標(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é)點。
- 當(dāng)前虛擬節(jié)點是
null、boolean
,這樣的值不應(yīng)該顯示在頁面當(dāng)中,創(chuàng)建注釋節(jié)點。 - 當(dāng)前
虛擬節(jié)點
是一個數(shù)組
,需要由Fragment
包裹。例如下面的寫法。
//自己寫render函數(shù) export default { render(){ return createVNode(Comp,null,{ default:()=>([ createVNode('div',null), createVNode('div',null) ]) }) } } //如果是正常編譯獲得的那么應(yīng)該是
- 如果是
object
,判斷當(dāng)前節(jié)點是否掛載過,掛載過需要克隆節(jié)點再返回。例如下面這種情況:
export default{ render(){ return createVNode(Comp,null,{ default:()=>createTextVNode('123') }) } }
- 如果是
字符串
或者number
,創(chuàng)建文本節(jié)點即可。例如下面這種情況:
//自己寫render函數(shù) export default { render(){ return createVNode(Comp,null,{ default:()=>123 }) } }
function normalizeVNode(child) { if (child == null || typeof child === "boolean") { //沒有child或者沒有實質(zhì)性內(nèi)容創(chuàng)建注釋節(jié)點 return createVNode(Comment); } else if (shared.isArray(child)) { //用戶直接寫了一個數(shù)組,需要包裹一層Fragment return createVNode(Fragment, null, child.slice()); } //如果這個節(jié)點已經(jīng)掛載過了克隆這個節(jié)點(復(fù)用節(jié)點) else if (typeof child === "object") { return cloneIfMounted(child); } //string 或者 number else { return createVNode(Text, null, String(child)); } }
- 到此為止我們就完成了對于對象形式的插槽標(biāo)準(zhǔn)化,并放到了實例的slots屬性上。 現(xiàn)在你可以通過訪問
slots.default
訪問到經(jīng)過標(biāo)準(zhǔn)化后的虛擬節(jié)點了。而我們實際在項目中使用的是<slot name="default"></slot>
,這個又是怎么渲染到頁面上的呢?大膽猜測一下就是根據(jù)name
屬性獲取到key
然后到instance.slots
中去找到這個虛擬節(jié)點
最后掛載到頁面就可以了。我們會在講解slots
的實現(xiàn)章節(jié)詳細解釋,這里就不過多講解了。
render(){ return createVNode(Comp,null,{ default:createVNode('div') }) } //經(jīng)過標(biāo)準(zhǔn)化后,相當(dāng)于 render(){ return createVNode(Comp,null,{ default:withCtx(()=>[createVNode('div')]) }) } //其他的情況都差不多,都是為了標(biāo)準(zhǔn)化為 //滿足上面四個條件的樣子
- 下面我們講解另一個分支,如果用戶用數(shù)組或字符串或數(shù)字作為
children
參數(shù)呢?createVNode(Comp,null,[])
就像這樣。又或者createVNode(Comp,null,123)
這樣。這就是標(biāo)識為ARRAY_CHILDREN
或TEXT_CHILDREN
的情況了,顯然調(diào)用了normalizeVNodeSlots
進行處理。 normalizeVNodeSlots
:這個情況我們可以把傳遞的第三個參數(shù)看成是調(diào)用對象形式的default函數(shù)
的返回值,那么我們只需要標(biāo)準(zhǔn)化第三個參數(shù)然后包裝成一個函數(shù),賦值給slots.default
就可以啦。
const normalizeVNodeSlots = (instance, children) => { const normalized = normalizeSlotValue(children); instance.slots.default = () => normalized; };
額外內(nèi)容
- 在
normalizeVNode
函數(shù)中,如果傳遞的child
是一個對象
,那么調(diào)用了cloneIfMounted
,這個函數(shù)是干什么的呢?如果el
有值,表示已經(jīng)有真實的DOM
了,那么就一定調(diào)用了render
函數(shù),也一定掛載
過元素了。我們看看他是如何克隆節(jié)點的呢?
//掛載過的vnode有el屬性 function cloneIfMounted(child) { return child.el === null || child.memo ? child : cloneVNode(child); }
cloneVNode
: 用于淺克隆一個VNode
。還可以提供額外的props合并之前VNode
身上的屬性。
- 如果提供了
extraProps
,調(diào)用mergeProps
合并之前的props
和新的props。對key
為class、style的屬性做了特殊處理。并且后面的props
可以覆蓋前面的props
- 當(dāng)
key
為class
的時候,之前的class
已經(jīng)經(jīng)過標(biāo)準(zhǔn)化了一定是一個字符串,我們需要將新的class與之前的class
合并為一個字符串。 - 當(dāng)
key
為style
的時候,合并新舊的style
對象。 - 其余情況,讓新的覆蓋舊的。
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屬性
進行合并,之前的ref
可能是數(shù)組(使用了v-for加ref),將最新的ref
添加到數(shù)組的后面,不是數(shù)組則轉(zhuǎn)化為數(shù)組在合并他們兩個ref
到這個數(shù)組中。 - 對于
靜態(tài)節(jié)點
,需要深度克隆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é)
- 本文我們主要介紹了如何對生成的組件實例的props和slots屬性進行初始化。
- 在初始化props中,根據(jù)定義組件的props和接受到的props放到
instance.props
中,對于定義了但是沒有傳遞,又有默認(rèn)值的我們需要使用默認(rèn)值。當(dāng)然我們還需要設(shè)置透傳屬性attrs
的值,如果傳遞了,但是沒有在props、emits
中定義,那么會認(rèn)為是透傳屬性,需要將其放入到instance.attrs
中。 - 然后我們詳細講解了slots的初始化。這一部分主要是對用戶自己使用
render
函數(shù)來渲染的模板,進行標(biāo)準(zhǔn)化保證后續(xù)的執(zhí)行不會出錯。 - 最后我們在額外內(nèi)容中介紹了
cloneVNode
的api
實現(xiàn)。 - 下文中我們將會繼續(xù)講解,對于其他組件定義的屬性的初始化。也就是
setupStatefulComponent
函數(shù),這里將會對watch、data、computed等屬性進行處理,調(diào)用setup函數(shù)、beforeCreat,created鉤子等。
以上就是Vue3源碼分析組件掛載初始化props與slots的詳細內(nèi)容,更多關(guān)于Vue3組件掛載初始化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別
這篇文章主要介紹了Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11vite+vue3搭建的工程實現(xiàn)批量導(dǎo)入store的module
這篇文章主要介紹了vite+vue3搭建的工程實現(xiàn)批量導(dǎo)入store的module方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03利用vue和element-ui設(shè)置表格內(nèi)容分頁的實例
下面小編就為大家分享一篇利用vue和element-ui設(shè)置表格內(nèi)容分頁的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03