【Vue原理】Slot - 源碼版之普通插槽

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

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

【Vue原理】Slot - 源碼版之普通插槽 學習

今天咱們來解讀Slot 的源碼啦。咱們都知道 Slot 分爲 普通Slot 和 做用域Slot,兩個內容都不少,因此分兩部分進行講述。this

今天講的是普通Slot!prototype

其實普通Slot,表示默認Slot和 具名Slot,只是他們的處理方式都差很少,就只是是否有自定義名字而已,因此,表示一種類型。3d

而咱們就以默認Slot爲例去探索,讓咱們先設置一個模板例子code

父組件模板component

image

test 組件被定義在父組件中blog

new Vue({    
    el: document.getElementsByTagName("div")[0],    
    components: {        
        test: {            
            template: ` 
                <main> 
                    我在子組件裏面 
                    <slot></slot>
                </main> `
        }
    },
    data() {        
        return {            
            name: 11
        }
    }
})

分兩個問題去看token

一、插槽內容怎麼解析

二、插槽如何插子頁面


插槽內容怎麼解析

插槽的做用域,是父實例。就是說,普通插槽的變量,都是從父實例上獲取的,好比上面例子插槽內的name

根據上面的例子,父組件被解析成下面的渲染函數

with(this) {    
    return _c('div', {},
        [ _c('test', [
            "我是放在組件的 slot " + name 
          ]) 
    ], 1)
}

父渲染函數執行時,會綁定父實例爲執行做用域,根據 with 的做用,test 的 slot 內的變量name,就會訪問父實例上的name。

那麼,當父渲染函數執行時,test組件的slot,全部變量訪問父實例,並開始解析,解析的流程跟普通的模板節點是同樣的


插槽怎麼插入子組件

當父渲染函數執行完畢,會獲得一個完整的VNode,上面存儲着描述DOM 的全部信息,用於去建立須要的DOM。

上面的父組件,會獲得這麼一個vnode

{    
    tag:'div',    
    children:[
       { 
          tag:'test',
          children:['我是放在組件的 slot 11'] 
       }
    ]
}

能夠看到

一、test組件, 被當作是 父組件的一個子元素

二、test 組件內的slot ,被當作是 test元素的子元素

雖然,並不會存在 test 這種標籤的元素,可是Vue統一對待,後面纔會特殊處理

一、test 組件內部解析

當父組件解析成功,獲得一個vnode,那麼下一步就是patch(建立DOM並插入頁面)

此時,Vue會按照渲染好的vnode,生成對應的DOM 樹,並插入到頁面中

當Vue遍歷到上面的vnode的children時,遇到了 test 這個節點,發現沒有test這種標籤,認定他是一個組件以後,會當作一個組件去解析

這個解析的流程不會戲說細說,不屬於Slot 的內容,後面的文章會講

二、Slot 轉存

解析 test 組件時,使用 _init 方法初始化 test 組件的實例

Vue.prototype._init = function(options) {    

    var vm = this;    

    if (若是是組件) {
        initInternalComponent(vm, options);
    }
    initRender(vm);
}

初始化test實例時,上面的兩個方法會起到轉存 Slot 的做用

一、initInternalComponent 把 test 組件插槽節點 【 ['我是放在組件的 slot 11'] 】 傳給組件選項的 【_renderChildren】 中

function initInternalComponent(vm, options) {    

// 這個options是全局選項和組件設置選項的合集
    var opts = vm.$options = 
            Object.create(vm.constructor.options);  

    var componentOptions = parentVnode.componentOptions;

    // 傳給組件選項_renderChildren
    opts._renderChildren = componentOptions.children;

}

二、initRender 把上一步保存在 組件選項的【_renderChildren】 放在實例的【$slot】中

function initRender(vm) {    

    var options = vm.$options;

    // 保存給組件實例上
    vm.$slots = resolveSlots(options._renderChildren, renderContext);
}

function resolveSlots(children, context) {    

    var slots = {};    

    for (var i = 0,l = children.length; i < l; i++) {        

        var child = children[i];        
        var data = child.data;   

        if (若是是具名slot) {} 
        else { 
            (slots.default || (slots.default = [])).push(child);
        }
    }    
    return slots
}

看父組件下的 test 組件的 vnode

{ 
    tag:'test',    
    children:['我是放在組件的 slot 11'] 
}

通過這兩步處理,插槽節點 轉存到了實例上(由於沒有給名字,因此默認是default,若是給了名字,就是你給的)

testVm.$slot={ 
    default: ['我是放在組件的 slot 11'] 
}

三、slot 替換到子組件

緊接着,test 實例化初始化完畢,開始使用組件模板去構建他的渲染函數

image

模板被解析成下面的渲染函數

with(this) {    
    return _c('main', [        
        "我在子組件裏面", 
        _t("default")
    ], 2)
}

你能夠看到,子組件的模板中的佔位符 slot,被解析成了 _t 函數

_t("default")

而後,test 渲染函數執行,其中 _t('default') 先執行

_t 是 renderSlot 函數,Vue 會給每一個實例都保存一個 _t

做用是根據傳入的名字,返回實例上$slot 保存的對應的 【插槽節點】

function installRenderHelpers(target) {
    target._t = renderSlot;
}

function renderSlot(name) {    
    return this.$slots[name]
}

_t('default') 執行完畢,返回插槽節點,因而 test 組件渲染函數就變成下面

with(this) {    
    return _c('main', [        
        "我在子組件裏面", 
        ['我是放在組件的 slot 11']
    ], 2)
}

如今,Slot 就徹底插入到子組件中啦,剩下的部分,就是渲染DOM 流程,已經跟 slot 沒有關係啦

用一張圖來看下流程

image

公衆號

相關文章
相關標籤/搜索