Vue3組件掛載之創(chuàng)建組件實例詳解
前情提要
上文我們講解了執(zhí)行createApp(App).mount('#root')
中的mount函數(shù)
,我們分析了創(chuàng)建虛擬節(jié)點
的幾個方法,以及setRef的執(zhí)行機制
、本文我們繼續(xù)講解mountComponent
,掛載組件的流程。
本文主要內(nèi)容
createComponentInstance
發(fā)生了什么?- 如何標(biāo)準(zhǔn)化組件定義的
props、emits
? - 為什么
provide
提供的值子組件都能訪問到? 組件
的v-model
實現(xiàn)原理、組件v-model修飾符
實現(xiàn)原理。- 組件
emit
實現(xiàn)原理。 once修飾符
實現(xiàn)原理。- 幾乎
所有組件實例屬性
的詳細講解。
mountComponent
- 這個方法主要用于
掛載組件
,根據(jù)傳遞的虛擬節(jié)點創(chuàng)建組件實例,調(diào)用setupComponent
函數(shù)進行初始化組件實例的插槽
和props
,如果是有狀態(tài)組件還需要處理setup的返回值
。最后調(diào)用setupRenderEffect
綁定副作用更新函數(shù)
。這個函數(shù)比較簡單,我們將重心放到createComponentInstance、setupComponent、setupRenderEffect
當(dāng)中、后文也是圍繞這三個方法進行依次講解。
const mountComponent = ( initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) => { //創(chuàng)建組件實例 const instance = (initialVNode.component = createComponentInstance( initialVNode, parentComponent, parentSuspense )); setupComponent(instance); if (instance.asyncDep) { //處理異步邏輯,suspense的時候在進行講解 } setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ); };
創(chuàng)建組件實例
createComponentInstance
: 根據(jù)虛擬節(jié)點創(chuàng)建組件實例的方法,簡單的說就是創(chuàng)建一個instance
對象,這個上面有許多的屬性
用來描述當(dāng)前這個組件
以及存儲需要后續(xù)使用到的屬性
,所以我們主要講解各個屬性的作用
,我們先來看看這個函數(shù)的源碼。uid
: 標(biāo)識當(dāng)前實例的id
,這是一個從0開始的遞增值
,同時他也是Vue調(diào)度器
的優(yōu)先級值
,越小的值優(yōu)先級越高
,試想一下,父組件先掛載,然后才會掛載子節(jié)點,子節(jié)點如果包含組件,那么uid
的值將會比父組件
的uid
值大,當(dāng)父組件掛載完畢,父組件的兄弟組件開始掛載,那么uid
將會更大
,這樣也符合流程更新邏輯,關(guān)于調(diào)度器我們之后單獨開一章節(jié)來進行講解。vnode
: 實例的虛擬節(jié)點
。parent
: 當(dāng)前組件實例的父組件實例。appContext
:createApp的時候注入的一個上下文
,我們通過app.component,app.mixin
調(diào)用后緩存的component
和mixin
都放在這里面,如果沒有parent
表示當(dāng)前是根組件
,那么就是這個appContext
,如果有parent
則繼承parent的appContext
。我們可以通過這里發(fā)現(xiàn)所有的組件的appContext
都會是唯一的一個context
。因此所有組件實例都將能訪問到全局注冊的component、mixin、directive
等。我們來回顧一下它的結(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é)點)
。而next
將會用于更新組件的props
以及slots
等屬性。組件自身狀態(tài)改變引起的更新是不需要更新props
的。subTree
: 調(diào)用render
函數(shù)之后返回的組件要渲染的虛擬根節(jié)點,可以通過這個屬性讀取到組件需要渲染的所有虛擬節(jié)點。effect
: 在setupRenderEffect
中創(chuàng)建的reactiveEffect
,我們在Vue3源碼分析(2)
中講解了reactiveEffect
,不了解的可以在閱讀一下,目前簡單理解為在調(diào)用render函數(shù)的時候收集依賴
,如果響應(yīng)式的值
發(fā)生了改變
會重新調(diào)用update函數(shù)
執(zhí)行更新流程
。我們將會在講解setupRenderEffect
中詳細講解這一部分。update
:組件更新函數(shù)
,只要調(diào)用了這個函數(shù),組件就會強制更新。同時響應(yīng)式發(fā)生改變調(diào)用的也是這個函數(shù)。render
:渲染函數(shù)
??梢酝ㄟ^編譯<template></template>
得到,也可以通過寫template屬性
獲得,也可以通過自己寫render
函數(shù)。exposed
: 組件指定的暴露到外部的屬性。provides
: 用戶在組件中設(shè)置了provide
,子代組件可以通過inject
收到傳遞的值,provides:parent ? parent.provides : Object.create(appContext.provides)
通過這段代碼分析我們可以知道,provides
中至少含有全局提供的provides
,如果當(dāng)前組件提供了provide
,后面會將其混合,并且繼承
自父組件
的provides
,這也就解釋了為什么provides可以向下傳遞
,因為每一層都可以收到本組件的provides
和父組件的provides
并進行合并
。components
: 如果你想在template
中使用組件,需要在這里注冊
,對于使用了<script setup>
語法糖的會將其編譯
到components屬性
當(dāng)中,當(dāng)然這個屬性就是用來存放這些注冊的組件的
。directives
: 存放自定義指令。propsOptions
:合并了mixins和extends后的props
,這里的屬性為什么是propsOptions
而不是props
呢?這個VNode
中的props
又有什么關(guān)系呢?實際上propsOptions
代表的是用戶在組件內(nèi)定義的props
,而VNode
中的props
是外部傳遞給組件的props
。這一點要加以區(qū)分。同時這里調(diào)用了normalizePropsOptions
來對propsOptions
進行標(biāo)準(zhǔn)化。接下來我們分析一下normalizePropsOptions
函數(shù)。這個函數(shù)比較長我們分成三個部分來分析。
- 首先從
appContext
中獲取props
緩存,避免處理過了組件props重復(fù)處理
。如果comp
不是一個函數(shù),這個判斷是因為,Vue3
中允許函數(shù)組件
的存在,但是函數(shù)組件是無狀態(tài)組件
也沒有props,所以不做處理。那么剩下的都是有狀態(tài)組件了,這里會處理全局注冊的mixins
,組件本身的mixins
和extends
。我們可以發(fā)現(xiàn)全局的最先處理,所以全局注冊的mixins
的優(yōu)先級
將會是最低
的,其次是extends
,顯然優(yōu)先級最高的則是組件自身的mixins
,因為他最后執(zhí)行,那么shared.extend
將會最后覆蓋之前的props
。我們還可以發(fā)現(xiàn)extends
屬性和mixins
屬性在實現(xiàn)上沒有任何區(qū)別,只是mixins
可以是數(shù)組,而extends
不能是數(shù)組。最后說一下asMixin
,我們知道全局的mixins
只需要合并一次
,但是normalizePropsOptions
會調(diào)用多次
,為了避免全局屬性混合
的多次執(zhí)行,設(shè)置了asMixin
這個參數(shù)。當(dāng)asMixin為true
的時候表示不需要
在合并全局的mixins
了。特別提示:shared.extend就是Object.assign
。
function normalizePropsOptions(comp, appContext, asMixin = false) { //獲取props的緩存 const cache = appContext.propsCache; const cached = cache.get(comp); //這個緩存是一個type對應(yīng)一個[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ù),因為extends和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); }; //首先合并全局注冊的mixins中的props屬性(最先合并的優(yōu)先級最低) if (!asMixin && appContext.mixins.length) { appContext.mixins.forEach(extendProps); } //然后合并extends屬性(中間合并的優(yōu)先級居中) //(extends功能與mixins幾乎一樣)但是更注重于繼承 //并且extends不能是數(shù)組 if (comp.extends) { extendProps(comp.extends); } //最后合并組件自身的mixins(最后合并的優(yōu)先級最高) if (comp.mixins) { comp.mixins.forEach(extendProps); } } //省略第二部分的代碼... }
- 我們知道這個函數(shù)主要是對
propsOptions
進行標(biāo)準(zhǔn)化,簡單的說就是將各式各樣的propsOptions
統(tǒng)一成唯一標(biāo)準(zhǔn)
。當(dāng)傳遞的props:['msg']
數(shù)組形式的時候,我們需要將其轉(zhuǎn)化為props:{msg:{}}
。validatePropName
用于檢測key
是否合法,Vue
中,組件傳遞參數(shù)不能以$
開頭定義數(shù)據(jù)。例如$msg
就是不合法
的。
function normalizePropsOptions(comp, appContext, asMixin = false) { //省略第一部分的代碼... //如果沒有props且沒有全局的mixins //組件本身的mixins、extends則設(shè)置 //當(dāng)前實例的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
,這個代表的是需要特殊處理的key,例如:props:{msg:{default:"msg"}}
含有default
,那么理論上我們應(yīng)當(dāng)判斷傳遞的屬性值是否存在,然后在決定是否使用default
的值,但是這里我們僅進行標(biāo)準(zhǔn)化,所以對于含有default
屬性的我們需要單獨放入needCastKeys
中,便于后面對props
中的處理。再比如說<Comp yes></Comp>
傳遞了yes屬性,在propsOptions
中props:{yes:{type:Boolean}}
這樣的key=>"yes"
也是需要處理的,yes
的值應(yīng)該為true
,所以對于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); //檢驗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
屬性。并且對emits
進行了標(biāo)準(zhǔn)化。主要是調(diào)用了normalizeEmitsOptions
進行處理,這個函數(shù)的邏輯和normlizePropsOptions
非常相似
,如果你看懂了上述的解釋,這個函數(shù)相信你很容易就能看懂,這里就不在做多余的解釋了。
export function normalizeEmitsOptions(comp, appContext, asMixin = false) { //獲取appContext中的緩存 const cache = appContext.emitsCache; const cached = cache.get(comp); //如果已經(jīng)讀取過返回緩存 if (cached !== undefined) { return cached; } //獲取組件的emits屬性{emits:['']} //還可以對象寫法{emits:{'':null||function(){}}} const raw = comp.emits; //最終的合并對象 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)先級最低) if (!asMixin && appContext.mixins.length) { appContext.mixins.forEach(extendEmits); } //然后合并實例的extends(優(yōu)先級居中) if (comp.extends) { extendEmits(comp.extends); } //最后合并組件自身的(優(yōu)先級最高) if (comp.mixins) { comp.mixins.forEach(extendEmits); } } if (!raw && !hasExtends) { //設(shè)置緩存 if (shared.isObject(comp)) { cache.set(comp, null); } return null; } //即使emits:[]是數(shù)組最終也會被轉(zhuǎn)化為對象 //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
: 如果你給組件傳遞了沒有在組件內(nèi)部聲明的屬性
,那么將會放入attrs
中。例如:
<Comp id="a"></Comp> //Comp組件 沒有對id的聲明,那么會放入attrs中 export default { props:{} }
setupState
:setup
的返回值。ctx
:當(dāng)前組件的上下文
。
- 通過
createDevRenderContext
函數(shù)創(chuàng)建。在這個函數(shù)當(dāng)中,對ctx
對象進行了代理
,可以通過ctx._
訪問到組件的實例,同時將publicPropertiesMap
上所有的屬性代理到了ctx
上,簡單的說就是之前需要通過publicPropertiesMap
訪問屬性,現(xiàn)在在ctx
上同樣能訪問到。這樣的代理方式在Vue
中被大量采用,setupState methods data computed watch props
當(dāng)中所有的屬性都將會采用這種方式被代理到ctx
上,當(dāng)然會出現(xiàn)重名問題
,所以保證上述的這些屬性中,應(yīng)當(dāng)避免重名
,否則會有警告
提示。當(dāng)然這些屬性是是在哪里被代理到ctx
上的,我們后面都會講到。
function createDevRenderContext(instance) { const target = {}; //可通過_訪問實例對象 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; }
- 不知道這個這個對象上的方法你是否很熟悉呢?沒錯,這就是
Vue
官方展示的組合式Api
,你可以在setup
中訪問this
調(diào)用到這些方法,不妨大膽猜測一下,為什么在setup
中訪問this
能讀取的這些方法?其實很簡單。setup.call(instance.ctx,其他參數(shù))
,實際上只要能訪問到this
的地方,基本上都是訪問的instance.ctx
、當(dāng)然有些地方的this
綁定的是instance.proxy
。這兩者的區(qū)別我們在后面在進行講解。其中的i
代表的是組件實例
。
const publicPropertiesMap = shared.extend(Object.create(null), { $: (i) => i, //獲取當(dāng)前實例 $el: (i) => i.vnode.el, $data: (i) => i.data, //獲取實例的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), //強制更新 $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ù)。通過this.$emit
調(diào)用的就是這個函數(shù)。對于emit
這個方法我們同樣分成三個部分
進行講解。
- 之前我們詳細講解了
emitsOptions
,他最終會被標(biāo)準(zhǔn)化。這也是標(biāo)準(zhǔn)化的作用,后續(xù)的處理都會變得很簡單,而不需要兼容多種寫法。如果調(diào)用了emit
方法,但是在emitsOptions
中沒有這個屬性,表示并沒有注冊,需要警告用戶
,同時如果你傳遞的emits
是一個函數(shù)
,那么他就是一個檢驗函數(shù)(如果不理解請查看Vue官網(wǎng)對于emits的解釋)
,傳遞的參數(shù)為調(diào)用emits
傳遞的剩余參數(shù)
。
function emit(instance, event, ...rawArgs) { //已經(jīng)卸載無須在執(zhí)行 if (instance.isUnmounted) return; //獲取props const props = instance.vnode.props || shared.EMPTY_OBJ; //獲取經(jīng)過標(biāo)準(zhǔn)化的emits和props //emits:{方法名:null||function(){}} //如果為function代表的是驗證函數(shù) const { emitsOptions, propsOptions: [propsOptions], } = instance; if (emitsOptions) { //警告用戶:調(diào)用了emit 但是沒有在emitOptions中找到,代表沒有聲明 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.` ); } } //獲取驗證函數(shù) else { const validator = emitsOptions[event]; if (shared.isFunction(validator)) { //調(diào)用驗證函數(shù),返回false則警告 const isValid = validator(...rawArgs); if (!isValid) { warn( `Invalid event arguments: event validation failed for event "${event}".` ); } } } } //省略第二部分代碼... }
- 這里的處理可能會讓你很懵逼,這個
isModelListener
是個啥?modifiersKey
又是個啥?在講解這個之前你需要了解v-model
在組件中的用法(你可以在Vue3官網(wǎng)查詢)
,以及他們的編譯結(jié)果
。通過編譯結(jié)果我們可以發(fā)現(xiàn),新增了屬性modelModifiers
,這就是咱們下面訪問的modifiersKey
了,同時v-model
指令本質(zhì)上會被編譯為onUpdate:modelValue
,所以我們可以通過判斷prop
的開頭是否是onUpdate
來判斷這是不是一個v-model
指令。有了這兩點我們就能理解第二部分的代碼在做什么了。首先判斷
當(dāng)前發(fā)射的事件是否是v-model
事件,如果是,獲取修飾符
對傳遞的參數(shù)
進行轉(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的時候 //<Button v-model.trim="a"></Button> //當(dāng)這樣傳遞的時候會收到modelModifiers={trim:true} const { number, trim } = props[modifiersKey] || shared.EMPTY_OBJ; //對emit('',...args)傳遞給父組件的參數(shù)執(zhí)行trim() if (trim) { args = rawArgs.map((a) => a.trim()); } if (number) { args = rawArgs.map(shared.toNumber); } } //省略第三部分代碼... }
- 首先通過event在props中找到需要發(fā)射的事件,如果存在執(zhí)行即可。如果添加了
once
修飾符,例如<C @close-Model.once="handler"></C>
他實際接收到的屬性是onCloseModelOnce
,而如果不寫once
修飾符就收到的就是onCloseModel
,所以添加了once
修飾符的執(zhí)行一次
后會被放入emitted
當(dāng)中,以后在觸發(fā)就不會在執(zhí)行
了。
function emit(instance, event, ...rawArgs) { //省略第二部分的代碼... //這里是簡單寫法,源碼實際上對 //event這個字符串做了改造。 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ù),作為檢驗函數(shù)
,檢驗不通過警告用戶。然后對組件使用v-model
指令做了處理,主要是對修飾符
的實現(xiàn)。當(dāng)然在HTML標(biāo)簽
中使用修飾符并不是在這里實現(xiàn)的,這里僅實現(xiàn)了組件的v-model修飾符
。最后調(diào)用傳遞父組件傳遞給子組件的函數(shù),并且把子組件要傳遞的參數(shù)給了父組件。如果含有once
修飾符,放入emitted緩存
中,只執(zhí)行一次。
inheritAttrs
:如果給組件傳遞了某些props
屬性,但是沒有給組件聲明props、emits、或者事件監(jiān)聽器
、那么id
屬性將會透傳到subTree
節(jié)點上,例如:<Comp id= "com"></Comp>
如果subTree
是一個div
節(jié)點,那么id
將會賦值到這個div
上。當(dāng)然你可以設(shè)置inheritAttrs為false
禁止透傳。存放生命周期的屬性
: 我們可以看到下面有一些簡寫的屬性例如:bc、c、bm
等,這些都是生命周期鉤子
,在這里需要注意的是,這些屬性的值
應(yīng)該是一個數(shù)組
,因為如果用戶使用了mixins
或者extends
這些屬性,那么同一個生命周期函數(shù)可能會包含多個
,而這些生命周期函數(shù)
都應(yīng)該被調(diào)用,所以他們的值應(yīng)當(dāng)是一個數(shù)組
例如:
export default { mixins:[{beforeCreate(){}}] beforeCreate(){} } //那么bc:[createBefore1,createBefore2]
- 還有一些
其他的屬性
,我都放在了注釋
當(dāng)中,因為看單詞意思就能理解,就不額外講解了,當(dāng)然后續(xù)這些屬性都將會用到,也許現(xiàn)在看這些屬性還是比較片面的。但是通過后面的講解,你將會逐漸了解這些屬性的作用。
function createComponentInstance(vnode, parent, suspense) { const type = vnode.type; const appContext = (parent ? parent.appContext : vnode.appContext) || {}; const instance = { uid: uid++, //當(dāng)前實例的id vnode, //當(dāng)前實例對應(yīng)的vnode type, //當(dāng)前實例對應(yīng)的編譯后的.vue生成的對象 parent, //當(dāng)前實例的父實例 appContext, //app的上下文包含全局注入的插件,自定義指令等 root: null, //當(dāng)前組件實例的根實例 //響應(yīng)式觸發(fā)的更新next為null, //在更新的過程中父組件調(diào)用了子組件的 //instance.update會賦值next為最新組件vnode next: null, subTree: null, //調(diào)用render函數(shù)后的Vnode(處理了透傳) effect: null, //實例的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,父實例有則讀取父實例的否則讀取app上的 //父組件的provides后續(xù)會掛載到prototype上,重新賦值當(dāng)前真實 //的provide上,這樣可以通過原型鏈訪問到所有上代組件中的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)前實例調(diào)用emit的函數(shù) emitted: null, //含有once修飾符的,執(zhí)行一次后放入這里不再執(zhí)行 propsDefaults: {}, //默認(rèn)props //是否透傳attrs inheritAttrs: type.inheritAttrs, // state ctx: {}, //當(dāng)前實例的上下文也就是this data: {}, //data函數(shù)返回的值,被代理后才放入 props: {}, //接受到的組件屬性 attrs: {}, //接受到的標(biāo)簽屬性 slots: {}, //組件傳遞的插槽內(nèi)容 refs: {}, //存入的refs setupState: {}, //setup的返回值 //expose attrs slots emit setupContext: null, //傳遞給setup的ctx(只有四個屬性) 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 //若組件實例是 <KeepAlive> 緩存樹的一部分, //當(dāng)組件從 DOM 中被移除時調(diào)用。deactivated da: null, //若組件實例是 <KeepAlive> 緩存樹的一部分, //當(dāng)組件被插入到 DOM 中時調(diào)用。activated a: null, //在一個響應(yīng)式依賴被組件觸發(fā)了重新渲染之后調(diào)用。 //renderTriggered rtg: null, //在一個響應(yīng)式依賴被組件的渲染作用追蹤后調(diào)用。 //renderTracked rtc: null, /** * 錯誤捕獲鉤子 * 組件渲染 * 事件處理器 * 生命周期鉤子 * setup() 函數(shù) * 偵聽器 * 自定義指令鉤子 * 過渡鉤子 * 錯誤捕獲鉤子 */ ec: null, //errorHandler sp: null, //serverPrefetch }; //創(chuàng)建實例的上下文 instance.ctx = createDevRenderContext(instance); //當(dāng)前實例的根實例 instance.root = parent ? parent.root : instance; instance.emit = emit.bind(null, instance); if (vnode.ce) { vnode.ce(instance); } return instance; }
總結(jié)
- 掛載組件一共執(zhí)行了三個主要的函數(shù):
createInstanceComponent(創(chuàng)建組件實例)、setupComponent(初始化組件)、setupRenderEffect(設(shè)置更新副作用)
。 - 本文我們重點講解了創(chuàng)建組件實例,以及對幾乎所有的屬性進行了詳細的講解,當(dāng)然
suspense相關(guān)屬性
我們會在講解suspense
的時候詳細為大家剖析。我們還重點講解了ctx對象
,其實觀察編譯結(jié)果,所有我們在template
中訪問的xxx變量
都會變成ctx.xxx
,本質(zhì)上就是將所有的可能用到的變量都代理到了ctx
上,代理的方式就是Object.defineProperty
。當(dāng)然還有如何標(biāo)準(zhǔn)化propsOptions、emitsOptions、
同時還詳細講解了組件的emit如何實現(xiàn)
。以及在組件中使用v-model
和它的修飾符trim、number
的實現(xiàn)過程,once
修飾符的實現(xiàn)原理。 - 下一小節(jié)我們將繼續(xù)剖析組件掛載的
第二個
流程-初始化組件
。
以上就是Vue3組件掛載之創(chuàng)建組件實例詳解的詳細內(nèi)容,更多關(guān)于Vue3 組件掛載創(chuàng)建實例的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
element-ui?tree?手動展開功能實現(xiàn)(異步樹也可以)
這篇文章主要介紹了element-ui?tree?手動進行展開(異步樹也可以),項目中用到了vue的element-ui框架,用到了el-tree組件,需要的朋友可以參考下2022-08-08Vue3如何使用axios發(fā)起網(wǎng)絡(luò)請求
這篇文章主要介紹了Vue3如何使用axios發(fā)起網(wǎng)絡(luò)請求,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06vue router-link 默認(rèn)a標(biāo)簽去除下劃線的實現(xiàn)
這篇文章主要介紹了vue router-link 默認(rèn)a標(biāo)簽去除下劃線的實現(xiàn)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11Vue中watch監(jiān)聽第一次不觸發(fā)、深度監(jiān)聽問題
這篇文章主要介紹了Vue中watch監(jiān)聽第一次不觸發(fā)、深度監(jiān)聽問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10