【Vue原理】Component - 源碼版 之 掛載組件DOM

寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】node

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧bash

【Vue原理】Component - 源碼版 之 掛載組件DOM dom

由這篇文章 從模板到DOM的簡要流程 函數

咱們知道,在生成 VNode 以後,下一步就是根據 VNode 生成DOM而後掛載了學習

在本文開始以前你能夠先看 Component - 白話版 先總體瞭解下componentui

如今開始咱們的正文this

上一篇文章 Component - 建立組件VNode ,咱們已經說到了 【頁面模板解析成 VNode 樹】的步驟spa

那今天就就到了 【頁面VNode生成DOM掛載】 了prototype

等等,今天說的不是 Component 掛載DOM 嗎?跟頁面Vnode 有什麼關係??是啊,component 的掛載確定是跟着父頁面的啊,你本身掛?自掛東南枝嗎?3d

好了,廢話不說,立刻開始


前言預告

這篇 從模板到DOM的簡要流程 已經說過下面的步驟

1vm._render 執行獲得 頁面VNode

2vm._update 拿到 頁面VNode ,會開始 patch,不斷比對 【舊VNode 和 剛拿到的新VNode】

對比完以後,會調用一個 createElm 的方法去建立DOM,而後插入頁面

那如今,咱們就從 createElm 這個方法突破,前面的流程跟本內容無關,一概略過

function createElm(vnode, parentElm, refElm) {    

    // 組件須要特殊處理

    if (createComponent(vnode, parentElm, refElm)) return

    ...正常的標籤,須要不斷遞歸子節點調用 createElm ,

             而後生成DOM,並插入到父節點

}
複製代碼

createElm 的做用就是根據 標籤名建立 DOM 節點,而後掛載到父節點中,其中參數以下

parentElm == 父DOM 節點
refElm == 兄弟DOM節點,你插入父節點,可能也要知道插在誰附近不是嗎,不能亂插的
複製代碼

而後很明顯,createElm 每次掉要給你都會調用 【createComponent】 去檢測這個標籤是不是組件

若是是組件,就會去建立這個組件的實例,而且 返回 true,從而不用去執行 createElm 下面的部分


調用組件生命鉤子

看下 createComponent

function createComponent(vnode, parentElm, refElm) {    

    var data = vnode.data;    

    var hook = i.hook;    

    var init = i.init;    

    // 調用子組件的 init 方法, init 方法就是 Vue.prototype._init

    if (init) {        

        // 建立子組件的 vm 實例

        init(vnode, parentElm, refElm);        

        // 若是存在組件實例,就是上一步建立成功了

        if (vnode.componentInstance) {            

            return true

        }
    }
}
複製代碼

有沒有好奇 vnode.data.hook.init 是什麼嗎?

他是每一個組件,都會被 【註冊進外殼節點的鉤子函數】,沒錯,就是下面的鉤子,源碼


什麼是組件生命鉤子

沒錯,這就是那個鉤子的源碼

var componentVNodeHooks = {

    init(vnode, parentElm, refElm) {        

        var vm= 

          vnode.componentInstance = 
          createComponentInstanceForVnode(

              vnode,activeInstance, 

              parentElm, refElm

          );        

        // 由於 在 Vue.prototype._init 中 ,只有 $options存在 el,纔會掛載 dom

        // 這裏手動掛載組件
        vm.$mount(vnode.elm);
    }
    ...

}
複製代碼

那麼,鉤子是何時註冊的呢?

嗯,在上一篇文章,【建立組件外殼VNode的過程當中】,而後保存到了外殼節點的 data 上

function createComponent(

    Ctor, data, context, 

    children, tag

) {


    ...建立組件構造函數
    var hooks = data.hook || (data.hook = {});
    data.hook.init = componentVNodeHooks.init

    ...建立組件VNode,並保存組件構造函數 和鉤子 等到 vnode 中

}
複製代碼

打印一下實際VNode,沒錯,有不少鉤子,可是如今只說 init

公衆號

來吧,仔細看那個init 鉤子源碼,你能夠看到調用了一個方法

createComponentInstanceForVnode

開始深刻探索它.........

建立組件實例

createComponentInstanceForVnode 函數做用就是給 component 【增長定製options】 + 【調用組件構造函數】

function createComponentInstanceForVnode(
    vnode, parent, 
    parentElm, refElm

) {    

    // 增長 component 特有options
    var options = {        

        _isComponent: true,        

        parent: parent, // 父實例

        _parentVnode: vnode, // 外殼節點
        _parentElm: parentElm , // 父DOM
        _refElm: refElm  // 兄弟DOM

    };    

    // vnode.components.Ctor 就是 構造函數 ,裏面會調用 Vue.prototype._init

    return new vnode.componentOptions.Ctor(options)
}
複製代碼

vnode.componentOptions.Ctor 就是 構造函數,就是下面這個,上篇文章 Component - 建立組件VNode 時保存在外殼節點的

function VueComponent(options) {    

    this._init(options); 

}
複製代碼

new 了以後,天然而然,走到了 _init 方法,在 init 方法中,有一個特殊照顧 component 的方法,專門給 component 實例設置options

"這一步跟 掛載組件DOM 沒什麼關聯,想去掉的,可是想一想仍是先保留下來,完整整個流程"

Vue.prototype._init = function(options) {    

    if (若是是組件) {

        initInternalComponent(vm, options);
    }
}
複製代碼

組件初始化 initInteralComponent

function initInternalComponent(vm, options) {    

    // 這個options 就是在建立構造函數時,合併的 options,全局選項和組件設置選項

    var opts = vm.$options = Object.create(vm.constructor.options);        
    
    // 保存父節點,外殼節點,兄弟節點等

    var parentVnode = options._parentVnode; // _parentVnode 是外殼節點
    opts.parent = options.parent; // options.parent 是 父實例
    opts._parentVnode = parentVnode;
    opts._parentElm = options._parentElm;

    opts._refElm = options._refElm;    

    // 保存父組件給子組件關聯的數據
    var vnodeComponentOptions = parentVnode.componentOptions;
    opts.propsData = vnodeComponentOptions.propsData;
    opts._parentListeners = vnodeComponentOptions.listeners;
    opts._renderChildren = vnodeComponentOptions.children;

    opts._componentTag = vnodeComponentOptions.tag;    

    // 保存渲染函數
    if (options.render) {
        opts.render = options.render;
        opts.staticRenderFns = options.staticRenderFns;
    }
}
複製代碼

這個時候, init 的過程就完成了

下一步就是到了 mount 過程


組件解析模板並掛載

能夠再回看下 「componentVNodeHooks.init 」 那個鉤子源碼

在建立組件實例成功以後,會手動調用實例 vm.$mount 進行掛載,就是這句代碼完成的功能

然而,掛載的步驟,就是正常標籤掛載的步驟了

詳情能夠查看 從模板到DOM的簡要流程 的 mount 過程,是一毛同樣的,就很少說了


總結

一、父頁面已經拿到了 VNode,其中會調用 createElm 根據 VNode 生成DOM,進行掛載

二、不斷的遞歸遍歷子節點,使用 createComponent 判斷標籤是不是組件

三、遇到組件,拿到組件外殼VNode 的data(data 保存有父組件給子組件的,事件,props,構造函數,鉤子)

四、從 data 中拿到 hook,hook 中拿到 init 鉤子,並執行 init 鉤子

五、init 鉤子中,調用 createComponentInstanceForVnode 調用組件構造函數,並返回組件

六、init 鉤子中,使用上一步返回的實例,手動調用 vm.$mount 進行組件內部模板解析渲染,並掛載

公衆號
相關文章
相關標籤/搜索