組件化

回憶

????????首先艇抠,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è)過程哄孤。

vue-cli初始化代碼

????????這段代碼相信很多同學(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趁窃。

?createElement?

????????在組件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ò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ù)有這么一段邏輯:

baseCtor?實(shí)際上就是 Vue

????????這里定義的是?Vue.option凌受,而我們的?createComponent?取的是?context.$options,實(shí)際上在?src/core/instance/init.js?里 Vue 原型上的?_init?函數(shù)中有這么一段邏輯:

?Vue 上的一些?option?擴(kuò)展到了 vm.$option?

????????這樣就把 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?中案怯。

extend

? ??????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é)會介紹奉件。

在extend中定義宵蛀,實(shí)例化sub時(shí)會執(zhí)行this._init

安裝組件鉤子函數(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ù):

安裝組件鉤子
componentVNodeHooks?的鉤子函數(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?并返回

????????最后一步非常簡單驰徊,通過?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?中:

createElm

????????我們刪掉多余的代碼,只保留關(guān)鍵的邏輯屎债,這里會判斷?createComponent(vnode, insertedVnodeQueue, parentElm, refElm)?的返回值仅政,如果為?true?則直接結(jié)束,那么接下來看一下?createComponent?方法的實(shí)現(xiàn):

createComponent

createComponent

首先對?vnode.data?做了一些判斷:

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ù)

????????init?鉤子函數(shù)執(zhí)行也很簡單辫封,我們先不考慮?keepAlive?的情況,它是通過?createComponentInstanceForVnode?創(chuàng)建一個(gè) 繼承于Vue 的實(shí)例廉丽,然后調(diào)用?$mount?方法掛載子組件倦微,先來看一下?createComponentInstanceForVnode?的實(shí)現(xiàn):

?createComponentInstanceForVnode?

????????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?中:

組件實(shí)例初始化執(zhí)行_init.js

????????這里首先是合并?options?的過程有變化侨把,_isComponent?為 true犀变,所以走到了?initInternalComponent?過程,這個(gè)函數(shù)的實(shí)現(xiàn)也簡單看一下:

initInternalComponent

? ? ? ? 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)行掛載省店。

_init掛載

????????由于組件初始化的時(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í)行?child.$mount偏塞,最終會調(diào)用?mountComponent?方法唱蒸,進(jìn)而執(zhí)行?vm._render()?方法
取到了vm.$options的render和_parentVnode,把_parentVnode賦值給vm.$vnode灸叼,通過vm.$creatElement取得組件的vnode神汹。通過vnode.parent=_parentVnode建立父子關(guān)系

????????我們知道在執(zhí)行完?vm._render?生成 VNode 后,接下來就要執(zhí)行?vm._update?去渲染 VNode 了古今。來看一下組件渲染的過程中有哪些需要注意的屁魏,vm._update的定義在?src/core/instance/lifecycle.js?中:

_update

????????_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但狭。還有一段比較有意思的代碼:

_update中的activeInstance即為父實(shí)例

????????這個(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)?方法:

initLifecycle

????????可以看到?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猴伶。

我們再來看看它的定義:

回顧creatElm

????????注意课舍,這里我們傳入的?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 的插入順序是先子后父比规。

總結(jié)

????????那么到此,一個(gè)組件的 VNode 是如何創(chuàng)建拦英、初始化蜒什、渲染的過程也就介紹完畢了。在對組件化的實(shí)現(xiàn)有一個(gè)大概了解后龄章,接下來我們來介紹一下這其中的一些細(xì)節(jié)吃谣。我們知道編寫一個(gè)組件實(shí)際上是編寫一個(gè) JavaScript 對象乞封,對象的描述就是各種配置,之前我們提到在?_init?的最初階段執(zhí)行的就是?merge options?的邏輯岗憋,那么下一節(jié)我們從源碼角度來分析合并配置的過程肃晚。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仔戈,隨后出現(xiàn)的幾起案子关串,更是在濱河造成了極大的恐慌,老刑警劉巖监徘,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晋修,死亡現(xiàn)場離奇詭異,居然都是意外死亡凰盔,警方通過查閱死者的電腦和手機(jī)墓卦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來户敬,“玉大人落剪,你說我怎么就攤上這事∧蚵” “怎么了忠怖?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抄瑟。 經(jīng)常有香客問我凡泣,道長,這世上最難降的妖魔是什么皮假? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任鞋拟,我火速辦了婚禮,結(jié)果婚禮上钞翔,老公的妹妹穿的比我還像新娘严卖。我一直安慰自己席舍,他們只是感情好布轿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著来颤,像睡著了一般汰扭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上福铅,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天萝毛,我揣著相機(jī)與錄音,去河邊找鬼滑黔。 笑死笆包,一個(gè)胖子當(dāng)著我的面吹牛环揽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庵佣,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼歉胶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巴粪?” 一聲冷哼從身側(cè)響起通今,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肛根,沒想到半個(gè)月后辫塌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡派哲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年臼氨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芭届。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡一也,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喉脖,到底是詐尸還是另有隱情椰苟,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布树叽,位于F島的核電站舆蝴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏题诵。R本人自食惡果不足惜洁仗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望性锭。 院中可真熱鬧赠潦,春花似錦、人聲如沸草冈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怎棱。三九已至哩俭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拳恋,已是汗流浹背凡资。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谬运,地道東北人隙赁。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓垦藏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親伞访。 傳聞我的和親對象是個(gè)殘疾皇子膝藕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 本文章是我最近在公司的一場內(nèi)部分享的內(nèi)容。我有個(gè)習(xí)慣就是每次分享都會先將要分享的內(nèi)容寫成文章咐扭。所以這個(gè)文集也是用來...
    Awey閱讀 9,426評論 4 67
  • 什么是組件晌畅? 組件 (Component) 是 Vue.js 最強(qiáng)大的功能之一渣触。組件可以擴(kuò)展 HTML 元素,封裝...
    youins閱讀 9,450評論 0 13
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對于 Vue 1.0 印象不深的內(nèi)容构罗。關(guān)于...
    云之外閱讀 5,045評論 0 29
  • [文/原創(chuàng)]阿九的樹屋 今天我接到一位來樹屋的客人豁延。他同我講已經(jīng)高三了昙篙,離高考只剩107天,壓力很大诱咏。不得不說我也...
    阿九的樹屋閱讀 1,007評論 12 24
  • 媒體報(bào)道:6月29日晚8點(diǎn)41分許袋狞,河南省駐馬店汽車運(yùn)輸公司豫Q52298號大型客車焚辅,由南往北行駛至京港澳高速衡東...
    大樹的森林閱讀 270評論 0 0