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

