回憶
????????首先艇抠,render函數(shù)中手寫h=>h(app)象颖,new Vue()實(shí)例初始化init()和原來一樣。$mount執(zhí)行到第一個(gè)$mount蚊惯,判斷有無render函數(shù)愿卸,沒有就生成render函數(shù),這里我們是有的截型。執(zhí)行第二個(gè)$mount趴荸,調(diào)用mountComponent,到了vm._update(vm._render(), hydrating)宦焦。
????????下面正式開始組件處理发钝,先是vm._render()生成組件vnode.這個(gè)過程主要做了三件事,1波闹、創(chuàng)建組件構(gòu)造函數(shù)(繼承于Vue)笼平。2、安裝組件鉤子函數(shù)(在patch流程中觸發(fā))舔痪。3寓调、組件vnode實(shí)例化。
? ? ? ? 其中細(xì)節(jié)锄码,vm._render()會調(diào)用vm.$createElement. 其中會判斷參數(shù)tag夺英,若是普通html標(biāo)簽則實(shí)例化一個(gè)普通vnode節(jié)點(diǎn),否則調(diào)用creatComponent創(chuàng)建組件vnode滋捶。1痛悯、通過Vue.extend得到一個(gè)繼承于Vue的組件構(gòu)造器(組件實(shí)例化時(shí)會執(zhí)行_init())。2重窟、遍歷組件鉤子函數(shù)(componentVNodeHooks中)载萌,若vnode相關(guān)data(即vNodeData)中當(dāng)前hook存在,把組件hook巡扇,merge到當(dāng)前hook上扭仁,不然直接賦值。3厅翔、通過new VNode()生成組件vnode并返回乖坠。
? ? ? ? 我們通過?createComponent?創(chuàng)建了組件 VNode,接下來會走到?vm._update刀闷,執(zhí)行?vm.__patch__?去把 VNode 轉(zhuǎn)換成真正的 DOM 節(jié)點(diǎn)熊泵。patch 的過程會調(diào)用?createElm?創(chuàng)建元素節(jié)點(diǎn),而createElm中會判斷?createComponent甸昏,true的話直接返回顽分,只執(zhí)行createComponent中邏輯。createComponent中會判斷是否是組件vnode,是的話?先執(zhí)行init(vnode,hydrating)施蜜。
? ? ? ? init(vnode,hydrating)卒蘸,通過?createComponentInstanceForVnode(vnode, activeInstance)創(chuàng)建一個(gè) 繼承于Vue 的實(shí)例,然后調(diào)用?$mount?方法掛載子組件花墩。
????????createComponentInstanceForVnode(vnode, activeInstance)執(zhí)行new vnode.componentOptions.Ctor(options)(這里的?vnode.componentOptions.Ctor?對應(yīng)的就是繼承于 Vue的子組件的構(gòu)造函數(shù))悬秉。其中options參數(shù),_isComponent?為?true?表示它是一個(gè)組件冰蘑,_parentVnode(new Vue的vnode和泌,其實(shí)為$el掛載點(diǎn)的vnode。如果為子組件祠肥,為子組件在父組件中的占位符)武氓,parent(activeInstance)?表示當(dāng)前激活的組件實(shí)例(即new Vue實(shí)例,我們現(xiàn)在 在處理的是new Vue實(shí)例中render中的app對象)(注意仇箱,這里比較有意思的是如何拿到組件實(shí)例县恕,后面會介紹。所以子組件的實(shí)例化實(shí)際上就是在這個(gè)時(shí)機(jī)執(zhí)行的剂桥,并且它會執(zhí)行實(shí)例的?_init?方法(在extend時(shí)定義的)忠烛。
? ? ? ?_init(),和new Vue初始化時(shí)有些不同,這里首先是合并?options?的過程有變化权逗,_isComponent?為 true美尸,所以走到了?initInternalComponent?過程。
? ??????initInternalComponent()斟薇,?opt即是當(dāng)前組件app實(shí)例的vm.$options师坎。這個(gè)過程我們重點(diǎn)記住以下幾個(gè)點(diǎn)即可:opts.parent = options.parent、opts._parentVnode = parentVnode堪滨。它們其實(shí)是把之前我們通過?createComponentInstanceForVnode?函數(shù)傳入的幾個(gè)參數(shù)(vnode,activeInstance)合并到內(nèi)部的選項(xiàng)?$options?里了胯陋。
? ??????_init()最后會執(zhí)行$mount,由于組件初始化的時(shí)候是不傳 el?的袱箱,因此組件是自己接管了?$mount?的過程遏乔,這個(gè)過程的主要流程在上一章介紹過了》⒈剩回到組件?init?的過程按灶,componentVNodeHooks?的?init?鉤子函數(shù),在完成實(shí)例化的?_init?后筐咧,接著會執(zhí)行?child.$mount(hydrating ? vnode.elm : undefined, hydrating)鸯旁。這里?hydrating?為 true 一般是服務(wù)端渲染的情況,我們只考慮客戶端渲染量蕊,所以這里?$mount?相當(dāng)于執(zhí)行?child.$mount(undefined, false)铺罢,它最終會調(diào)用?mountComponent?方法,進(jìn)而執(zhí)行?vm._render()?方法残炮。
? ?????vm._render() 中取到了vm.$options的render和_parentVnode(new Vue的vnode韭赘,其實(shí)為$el掛載點(diǎn)的vnode),把_parentVnode賦值給vm.$vnode(當(dāng)前vm實(shí)例是app組件實(shí)例)势就,通過vm.$creatElement取得組件的vnode(這個(gè)app組件的vnode)泉瞻。通過vnode.parent=_parentVnode建立父子關(guān)系脉漏。
? ?????我們知道在執(zhí)行完?vm._render?生成 VNode 后,接下來就要執(zhí)行?vm._update?去渲染 VNode 了袖牙。
????????_update() 過程中有幾個(gè)關(guān)鍵的代碼侧巨,首先?vm._vnode = vnode?的邏輯,這個(gè)?vnode?是通過?vm._render()?返回的組件渲染 VNode鞭达,vm._vnode(組件app實(shí)際渲染vnode) 和?vm.$vnode(new Vue的vnode司忱,其實(shí)為$el掛載點(diǎn)的vnode) 的關(guān)系就是一種父子關(guān)系,用代碼表達(dá)就是?vm._vnode.parent === vm.$vnode畴蹭。另外坦仍,_update()中這個(gè)?activeInstance(vm)?作用就是保持當(dāng)前上下文的 Vue 實(shí)例(在處理app組件時(shí),它是new Vue實(shí)例,在處理app組件進(jìn)行_update時(shí)它又賦值成當(dāng)前app組件, 若下面又有組件叨襟,把它作為parent傳下去)繁扎,它是在?lifecycle?模塊的全局變量( export let activeInstance: any = null),并且在之前我們調(diào)用?createComponentInstanceForVnode?方法的時(shí)候從?lifecycle?模塊獲取糊闽,并且作為參數(shù)parent傳入的锻离。因?yàn)閷?shí)際上 JavaScript 是一個(gè)單線程,Vue 整個(gè)初始化是一個(gè)深度遍歷的過程墓怀,在實(shí)例化子組件的過程中汽纠,它需要知道當(dāng)前上下文的 Vue 實(shí)例是什么,并把它作為子組件的父 Vue 實(shí)例傀履。之前我們提到過對子組件的實(shí)例化過程先會調(diào)用?initInternalComponent(vm, options)?合并?options虱朵,把?parent?存儲在?vm.$options?中,在 _init()中會調(diào)用?initLifecycle(vm)?
?????????initLifecycle(vm)中可以看到?vm.$parent?就是用來保留當(dāng)前?vm?的父實(shí)例钓账,并且通過?parent.$children.push(vm)?來把當(dāng)前的?vm?存儲到父實(shí)例的?$children?中碴犬。
????????在?vm._update?的過程中,把當(dāng)前的?vm?賦值給?activeInstance梆暮,同時(shí)通過?const?prevActiveInstance=activeInstance用?prevActiveInstance?保留上一次的?activeInstance服协。實(shí)際上,prevActiveInstance?和當(dāng)前的?vm?是一個(gè)父子關(guān)系啦粹,當(dāng)一個(gè)?vm?實(shí)例完成它的所有子樹的 patch 或者 update 過程后偿荷,activeInstance?會回到它的父實(shí)例,這樣就完美地保證了?createComponentInstanceForVnode?整個(gè)深度遍歷過程中唠椭,我們在實(shí)例化子組件的時(shí)候能傳入當(dāng)前子組件的父 Vue 實(shí)例跳纳,并在?_init?的過程中,通過?vm.$parent?把這個(gè)父子關(guān)系保留.
????????那么回到?_update贪嫂,最后就是調(diào)用?__patch__?渲染 VNode 了寺庄。這里又回到了本節(jié)開始的過程,之前分析過負(fù)責(zé)渲染成 DOM 的函數(shù)是?createElm(),注意這里我們只傳了 2 個(gè)參數(shù)斗塘,所以對應(yīng)的?parentElm?是?undefined赢织。
????????createElm(),這里我們傳入的?vnode?是組件渲染的?vnode馍盟,也就是我們之前說的?vm._vnode于置,如果組件的根節(jié)點(diǎn)是個(gè)普通元素,那么?vm._vnode?也是普通的?vnode朽合,這里?createComponent(vnode, insertedVnodeQueue, parentElm, refElm)?的返回值是 false。? ? ? ?
????????接下來的過程就和我們上一章一樣了饱狂,先創(chuàng)建一個(gè)父節(jié)點(diǎn)占位符曹步,然后再遍歷所有子 VNode 遞歸調(diào)用?createElm,在遍歷的過程中休讳,如果遇到子 VNode 是一個(gè)組件的 VNode讲婚,則重復(fù)本節(jié)開始的過程,這樣通過一個(gè)遞歸的方式就可以完整地構(gòu)建了整個(gè)組件樹俊柔。
????????由于我們這個(gè)時(shí)候傳入的?parentElm?是空筹麸,所以對組件的插入,在?createComponent 中雏婶,在完成組件的整個(gè)?patch?過程后物赶,最后執(zhí)行?insert(parentElm, vnode.elm, refElm)?完成組件的 DOM 插入,如果組件?patch?過程中又創(chuàng)建了子組件留晚,那么DOM 的插入順序是先子后父酵紫。
概述:
????????Vue.js 另一個(gè)核心思想是組件化。所謂組件化错维,就是把頁面拆分成多個(gè)組件 (component)奖地,每個(gè)組件依賴的 CSS、JavaScript赋焕、模板参歹、圖片等資源放在一起開發(fā)和維護(hù)。組件是資源獨(dú)立的隆判,組件在系統(tǒng)內(nèi)部可復(fù)用犬庇,組件和組件之間可以嵌套。
????????我們在用 Vue.js 開發(fā)實(shí)際項(xiàng)目的時(shí)候侨嘀,就是像搭積木一樣械筛,編寫一堆組件拼裝生成頁面。在 Vue.js 的官網(wǎng)中飒炎,也是花了大篇幅來介紹什么是組件埋哟,如何編寫組件以及組件擁有的屬性和特性。
????????在這一章節(jié),我們將從源碼的角度來分析 Vue 的組件內(nèi)部是如何工作的赤赊,只有了解了內(nèi)部的工作原理闯狱,才能讓我們使用它的時(shí)候更加得心應(yīng)手。
? ??????接下來我們會用 Vue-cli 初始化的代碼為例抛计,來分析一下 Vue 組件初始化的一個(gè)過程哄孤。
????????這段代碼相信很多同學(xué)都很熟悉,它也是通過?render?函數(shù)去渲染的吹截,不同的這次通過?createElement?傳的參數(shù)是一個(gè)組件而不是一個(gè)原生的標(biāo)簽瘦陈,那么接下來我們就開始分析這一過程。
createComponent
? ? ? ? 首先生成vnode?波俄,會先執(zhí)行render函數(shù)晨逝,render對于自己寫render函數(shù)的會執(zhí)行vm.$createElement ,而它最終會調(diào)用?_createElement?方法,其中有一段邏輯是對參數(shù)?tag?的判斷懦铺,如果是一個(gè)普通的 html 標(biāo)簽捉貌,像上一章的例子那樣是一個(gè)普通的 div,則會實(shí)例化一個(gè)普通 VNode 節(jié)點(diǎn)冬念,否則通過?createComponent?方法創(chuàng)建一個(gè)組件 VNode趁窃。
????????在組件render函數(shù)中傳入的是一個(gè) App 對象,它本質(zhì)上是一個(gè)?Component?類型急前,那么它會走到上述代碼的 else 邏輯醒陆,直接通過?createComponent?方法來創(chuàng)建?vnode。所以接下來我們來看一下?createComponent?方法的實(shí)現(xiàn)裆针,它定義在?src/core/vdom/create-component.js?文件中:
????????createComponent?的邏輯也會有一些復(fù)雜统求,但是分析源碼比較推薦的是只分析核心流程,分支流程可以之后針對性的看据块,所以這里針對組件渲染這個(gè) case 主要就 3 個(gè)關(guān)鍵步驟:構(gòu)造子類構(gòu)造函數(shù)码邻,安裝組件鉤子函數(shù)和實(shí)例化?vnode。
構(gòu)造子類構(gòu)造函數(shù)
????????我們在編寫一個(gè)組件的時(shí)候另假,通常都是創(chuàng)建一個(gè)普通對象像屋,還是以我們的 App.vue 為例,代碼如下:
????????這里 export 的是一個(gè)對象边篮,所以?createComponent?里的代碼邏輯會執(zhí)行到?baseCtor.extend(Ctor)己莺,在這里?baseCtor?實(shí)際上就是 Vue,這個(gè)的定義是在最開始初始化 Vue 的階段戈轿,在?src/core/global-api/index.js?中的?initGlobalAPI?函數(shù)有這么一段邏輯:
????????這里定義的是?Vue.option凌受,而我們的?createComponent?取的是?context.$options,實(shí)際上在?src/core/instance/init.js?里 Vue 原型上的?_init?函數(shù)中有這么一段邏輯:
????????這樣就把 Vue 上的一些?option?擴(kuò)展到了 vm.$option 上思杯,所以我們也就能通過?vm.$options._base?拿到 Vue 這個(gè)構(gòu)造函數(shù)了胜蛉。mergeOptions?的實(shí)現(xiàn)我們會在后續(xù)章節(jié)中具體分析挠进,現(xiàn)在只需要理解它的功能是把 Vue 構(gòu)造函數(shù)的?options?和用戶傳入的?options?做一層合并,到?vm.$options?上誊册。
????????在了解了?baseCtor?指向了 Vue 之后领突,我們來看一下?Vue.extend?函數(shù)的定義,在?src/core/global-api/extend.js?中案怯。
? ??????Vue.extend?的作用就是構(gòu)造一個(gè)?Vue?的子類君旦,它使用一種非常經(jīng)典的原型繼承的方式把一個(gè)純對象轉(zhuǎn)換一個(gè)繼承于?Vue?的構(gòu)造器?Sub?并返回,然后對?Sub?這個(gè)對象本身擴(kuò)展了一些屬性嘲碱,如擴(kuò)展?options金砍、添加全局 API 等;并且對配置中的?props?和?computed?做了初始化工作麦锯;最后對于這個(gè)?Sub?構(gòu)造函數(shù)做了緩存恕稠,避免多次執(zhí)行?Vue.extend?的時(shí)候?qū)ν粋€(gè)子組件重復(fù)構(gòu)造。
????????這樣當(dāng)我們?nèi)?shí)例化?Sub?的時(shí)候离咐,就會執(zhí)行?this._init?邏輯再次走到了?Vue?實(shí)例的初始化邏輯谱俭,實(shí)例化子組件的邏輯在之后的章節(jié)會介紹奉件。
安裝組件鉤子函數(shù)
????????我們之前提到 Vue.js 使用的 Virtual DOM 參考的是開源庫?snabbdom,它的一個(gè)特點(diǎn)是在 VNode 的 patch 流程中對外暴露了各種時(shí)機(jī)的鉤子函數(shù)县貌,方便我們做一些額外的事情术陶,Vue.js 也是充分利用這一點(diǎn),在初始化一個(gè) Component 類型的 VNode 的過程中實(shí)現(xiàn)了幾個(gè)鉤子函數(shù):
????????整個(gè)?mergeHooks?的過程就是把?componentVNodeHooks?的鉤子函數(shù)合并到?data.hook?中煤痕,在 VNode 執(zhí)行?patch?的過程中執(zhí)行相關(guān)的鉤子函數(shù)梧宫,具體的執(zhí)行我們稍后在介紹?patch?過程中會詳細(xì)介紹。這里要注意的是合并策略摆碉,在合并過程中塘匣,如果某個(gè)時(shí)機(jī)的鉤子已經(jīng)存在?data.hook?中,那么通過執(zhí)行?mergeHook?函數(shù)做合并巷帝,這個(gè)邏輯很簡單忌卤,就是在最終執(zhí)行的時(shí)候,依次執(zhí)行這兩個(gè)鉤子函數(shù)即可楞泼。
實(shí)例化 VNode
????????最后一步非常簡單驰徊,通過?new VNode?實(shí)例化一個(gè)?vnode?并返回。需要注意的是和普通元素節(jié)點(diǎn)的?vnode?不同堕阔,組件的?vnode?是沒有?children?的棍厂,這點(diǎn)很關(guān)鍵,在之后的?patch?過程中我們會再提超陆。
patch
? ? ? ? 當(dāng)我們通過?createComponent?創(chuàng)建了組件 VNode牺弹,接下來會走到?vm._update,執(zhí)行?vm.__patch__?去把 VNode 轉(zhuǎn)換成真正的 DOM 節(jié)點(diǎn)。這個(gè)過程我們在前一章已經(jīng)分析過了例驹,但是針對一個(gè)普通的 VNode 節(jié)點(diǎn)捐韩,接下來我們來看看組件的 VNode 會有哪些不一樣的地方。
? ??????patch 的過程會調(diào)用?createElm?創(chuàng)建元素節(jié)點(diǎn)鹃锈,回顧一下?createElm?的實(shí)現(xiàn)荤胁,它的定義在?src/core/vdom/patch.js?中:
????????我們刪掉多余的代碼,只保留關(guān)鍵的邏輯屎债,這里會判斷?createComponent(vnode, insertedVnodeQueue, parentElm, refElm)?的返回值仅政,如果為?true?則直接結(jié)束,那么接下來看一下?createComponent?方法的實(shí)現(xiàn):
createComponent
首先對?vnode.data?做了一些判斷:
????????如果?vnode?是一個(gè)組件 VNode盆驹,那么條件會滿足圆丹,并且得到?i?就是?init?鉤子函數(shù),回顧上節(jié)我們在創(chuàng)建組件 VNode 的時(shí)候合并鉤子函數(shù)中就包含?init?鉤子函數(shù)躯喇,定義在?src/core/vdom/create-component.js?中:
????????init?鉤子函數(shù)執(zhí)行也很簡單辫封,我們先不考慮?keepAlive?的情況,它是通過?createComponentInstanceForVnode?創(chuàng)建一個(gè) 繼承于Vue 的實(shí)例廉丽,然后調(diào)用?$mount?方法掛載子組件倦微,先來看一下?createComponentInstanceForVnode?的實(shí)現(xiàn):
????????createComponentInstanceForVnode?函數(shù)構(gòu)造的一個(gè)內(nèi)部組件的參數(shù),然后執(zhí)行?new vnode.componentOptions.Ctor(options)正压。這里的?vnode.componentOptions.Ctor?對應(yīng)的就是子組件的構(gòu)造函數(shù)欣福,我們上一節(jié)分析了它實(shí)際上是繼承于 Vue 的一個(gè)構(gòu)造器?Sub,相當(dāng)于?new Sub(options)?這里有幾個(gè)關(guān)鍵參數(shù)要注意幾個(gè)點(diǎn)焦履,_isComponent?為?true?表示它是一個(gè)組件拓劝,parent(activeInstance)?表示當(dāng)前激活的組件實(shí)例(即new Vue實(shí)例,我們現(xiàn)在 在處理的是new Vue實(shí)例中render中的app對象)(注意嘉裤,這里比較有意思的是如何拿到組件實(shí)例郑临,后面會介紹。
????????所以子組件的實(shí)例化實(shí)際上就是在這個(gè)時(shí)機(jī)執(zhí)行的屑宠,并且它會執(zhí)行實(shí)例的?_init?方法(在extend時(shí)定義的)厢洞,這個(gè)過程有一些和之前不同的地方需要挑出來說,代碼在?src/core/instance/init.js?中:
????????這里首先是合并?options?的過程有變化侨把,_isComponent?為 true犀变,所以走到了?initInternalComponent?過程,這個(gè)函數(shù)的實(shí)現(xiàn)也簡單看一下:
? ? ? ? opt即是當(dāng)前處理實(shí)例app的vm.$options秋柄。這個(gè)過程我們重點(diǎn)記住以下幾個(gè)點(diǎn)即可:opts.parent = options.parent获枝、opts._parentVnode = parentVnode,它們是把之前我們通過?createComponentInstanceForVnode?函數(shù)傳入的幾個(gè)參數(shù)(vnode,activeInstance)合并到內(nèi)部的選項(xiàng)?$options?里了骇笔。
? ? ? ? _init最后會進(jìn)行掛載省店。
????????由于組件初始化的時(shí)候是不傳 el 的嚣崭,因此組件是自己接管了?$mount?的過程,這個(gè)過程的主要流程在上一章介紹過了懦傍,回到組件?init?的過程雹舀,componentVNodeHooks?的?init?鉤子函數(shù),在完成實(shí)例化的?_init?后粗俱,接著會執(zhí)行?child.$mount(hydrating ? vnode.elm : undefined, hydrating)说榆。這里?hydrating?為 true 一般是服務(wù)端渲染的情況,我們只考慮客戶端渲染寸认,所以這里?$mount?相當(dāng)于執(zhí)行?child.$mount(undefined, false)签财,它最終會調(diào)用?mountComponent?方法,進(jìn)而執(zhí)行?vm._render()?方法
????????我們知道在執(zhí)行完?vm._render?生成 VNode 后,接下來就要執(zhí)行?vm._update?去渲染 VNode 了古今。來看一下組件渲染的過程中有哪些需要注意的屁魏,vm._update的定義在?src/core/instance/lifecycle.js?中:
????????_update?過程中有幾個(gè)關(guān)鍵的代碼,首先?vm._vnode = vnode?的邏輯沧卢,這個(gè)?vnode?是通過?vm._render()?返回的組件渲染 VNode蚁堤,vm._vnode?和?vm.$vnode?的關(guān)系就是一種父子關(guān)系醉者,用代碼表達(dá)就是?vm._vnode.parent === vm.$vnode但狭。還有一段比較有意思的代碼:
????????這個(gè)?activeInstance?作用就是保持當(dāng)前上下文的 Vue 實(shí)例,它是在?lifecycle?模塊的全局變量撬即,定義是?export let activeInstance: any = null立磁,并且在之前我們調(diào)用?createComponentInstanceForVnode?方法的時(shí)候從?lifecycle?模塊獲取,并且作為參數(shù)傳入的剥槐。因?yàn)閷?shí)際上 JavaScript 是一個(gè)單線程唱歧,Vue 整個(gè)初始化是一個(gè)深度遍歷的過程,在實(shí)例化子組件的過程中粒竖,它需要知道當(dāng)前上下文的 Vue 實(shí)例是什么颅崩,并把它作為子組件的父 Vue 實(shí)例。之前我們提到過對子組件的實(shí)例化過程先會調(diào)用?initInternalComponent(vm, options)?合并?options蕊苗,把?parent?存儲在?vm.$options?中沿后,在?$mount?之前會調(diào)用?initLifecycle(vm)?方法:
????????可以看到?vm.$parent?就是用來保留當(dāng)前?vm?的父實(shí)例,并且通過?parent.$children.push(vm)?來把當(dāng)前的?vm?存儲到父實(shí)例的?$children?中朽砰。
????????在?vm._update?的過程中尖滚,把當(dāng)前的?vm?賦值給?activeInstance喉刘,同時(shí)通過?const prevActiveInstance=activeInstance?用?prevActiveInstance?保留上一次的?activeInstance。實(shí)際上漆弄,prevActiveInstance?和當(dāng)前的?vm?是一個(gè)父子關(guān)系睦裳,當(dāng)一個(gè)?vm?實(shí)例完成它的所有子樹的 patch 或者 update 過程后,activeInstance?會回到它的父實(shí)例撼唾,這樣就完美地保證了?createComponentInstanceForVnode?整個(gè)深度遍歷過程中廉邑,我們在實(shí)例化子組件的時(shí)候能傳入當(dāng)前子組件的父 Vue 實(shí)例,并在?_init?的過程中倒谷,通過?vm.$parent?把這個(gè)父子關(guān)系保留.
那么回到?_update鬓催,最后就是調(diào)用?__patch__?渲染 VNode 了。
我們再來看看它的定義:
????????注意课舍,這里我們傳入的?vnode?是組件渲染的?vnode,也就是我們之前說的?vm._vnode他挎,如果組件的根節(jié)點(diǎn)是個(gè)普通元素筝尾,那么?vm._vnode?也是普通的?vnode,這里?createComponent(vnode, insertedVnodeQueue, parentElm, refElm)?的返回值是 false办桨。
????????接下來的過程就和我們上一章一樣了筹淫,先創(chuàng)建一個(gè)父節(jié)點(diǎn)占位符,然后再遍歷所有子 VNode 遞歸調(diào)用?createElm呢撞,在遍歷的過程中损姜,如果遇到子 VNode 是一個(gè)組件的 VNode,則重復(fù)本節(jié)開始的過程殊霞,這樣通過一個(gè)遞歸的方式就可以完整地構(gòu)建了整個(gè)組件樹摧阅。
????????由于我們這個(gè)時(shí)“候傳入的?parentElm?是空,所以對組件的插入绷蹲,在?createComponent?有這么一段邏輯:
總結(jié)
????????那么到此,一個(gè)組件的 VNode 是如何創(chuàng)建拦英、初始化蜒什、渲染的過程也就介紹完畢了。在對組件化的實(shí)現(xiàn)有一個(gè)大概了解后龄章,接下來我們來介紹一下這其中的一些細(xì)節(jié)吃谣。我們知道編寫一個(gè)組件實(shí)際上是編寫一個(gè) JavaScript 對象乞封,對象的描述就是各種配置,之前我們提到在?_init?的最初階段執(zhí)行的就是?merge options?的邏輯岗憋,那么下一節(jié)我們從源碼角度來分析合并配置的過程肃晚。