【Vue原理】Slot - 源碼版之做用域插槽

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

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

【Vue原理】Slot - 源碼版之做用域插槽 函數

今天探索Slot的另外一部分,做用域插槽。學習

首先,設置一個模板例子this

image

把子組件的 child 傳給 插槽prototype

image

父組件會解析成下面的渲染函數3d

with(this) {    

    return _c('div', {},

        [_c('test', { 

            scopedSlots: _u([{                

                key: "default",                

                fn: function(slotProps) {                   

                    return [ "我是放在組件的 slot :"+slotProps ]

                }
            }])

        })],    

    1)

}

其中,_u 是 resolveScopedSlots,Vue會給每一個實例都註冊一個 _u 方法。code

做用主要是把數組變成對象map並返回對象

看下 resolveScopedSlots 源碼blog

給每一個實例註冊 _u

image

function resolveScopedSlots(fns, res) {
    res = res || {};    
    for (var i = 0; i < fns.length; i++) { 

        res[fns[i].key] = fns[i].fn;
    }    
    return res
}

把傳入的數組組裝成對象,像是下面這樣

[{    
    key: "default",    
    fn: function(slotProps) {        
        return ["我是放在組件的 slot :" + slotProps]

    }
}]

---------_u 把上面變成下面------

{    
    default:function(slotProps) {        
        return ["我是放在組件的 slot :" + slotProps]
    }
}

插槽怎麼解析

你能夠看到了,在父組件的渲染函數中,做用域Slot 被包裝成了一個函數,而且保存在 test 組件的 scopeSlot 中,用於後面解析內部組件時使用

包裝成函數,是爲了改變插槽內容的變量訪問的做用域。

經過函數參數傳遞的形式,讓插槽的變量,在解析時,先訪問函數變量。若是沒有,再去父組件上獲取

那麼這個函數的參數是從哪裏傳進來的呢?讓咱們一探究竟

額外:組件解析

上面的 test 在父組件中解析成一個節點

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

而解析 test 時,也會被解析成一個節點

{    
    tag:'main',    
    children:["我在子組件裏面","我是放在組件的 slot :11"]
}

就是 test 會有兩個節點,第一個節點我認爲是外殼節點,第二個節點是渲染節點,是真正插入頁面的。

那麼這兩個有什麼關係呢?外殼節點保存着全部父組件裏給這個子組件綁定的數據,好比 props,插槽等。而後提供給 組件解析時使用

按順序理一下解析流程

一、插槽函數保存到外殼節點

以前的父渲染函數,子組件的插槽解析成一個節點處理函數,以下 ,而後做爲 scopedSlots 保存在 test 組件的外殼節點上

{    
    tag:'test',    
    data:{
        scopedSlots:{
            // 插槽包裝成的函數
            default:function(slotProps) {                   
                return [ "我是放在組件的 slot :"+slotProps ]
            }
        }
    },
    children:["我是放在組件的 slot :11"]
}

二、插槽函數另存爲

而後,test組件會建立自身實例,而且初始化,在初始化的過程當中,會把 外殼節點上的 $scopedSlots 另存爲到本實例上,方便後面子組件解析內部模板直接調用

// 這個函數做用是,執行渲染函數,獲得組件節點
Vue.prototype._render = function() {    

    var vm = this;    
    var ref = vm.$options; 

    // _parentVnode 就是外殼節點
    var _parentVnode = ref._parentVnode; 

    if (_parentVnode) {
        vm.$scopedSlots = _parentVnode.data.scopedSlots || {};
    }
    ...省略N多執行渲染函數的代碼
    vm.$vnode = _parentVnode;    
    return vnode
};

三、子組件解析內部

看下子組件模板,綁定了child在 slot 上,用於傳給插槽

image

執行子組件解析成的渲染函數以下

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

其中,child 會從子組件上獲取,因此 child 是11

渲染函數中,看到子組件中的slot的佔位標籤以下

<slot :child=child ></slot>

被解析成了一個_t函數(怎麼解析的話,又是一篇,太多先不說)

_t('default', null, { child:child })

看下_t,他是renderSlot,上一篇文章提過。這個方法,會兼容處理做用域Slot和普通Slot,上篇文章省略了處理做用域Slot 的代碼,如今看一下

function renderSlot(name, fallback, props) {   

    // 看了上面,因此能夠從實例上獲取$scopedSlots
    var scopedSlotFn = this.$scopedSlots[name];   
    var nodes;    

    if (scopedSlotFn) {
        props = props || {};

        // 開始執行插槽函數
        nodes = scopedSlotFn(props);
    }    
    return nodes
}

_t 的做用是,執行會直接返回節點,直接替換子組件 slot 佔位符,完成插入功能

_t 就是 renderSlot ,函數會根據 【插槽名字】 找到對應的 【做用域Slot包裝成的函數】,而後執行它,把子組件內的數據 【 { child:child } 】子傳進去

因而,做用域Slot 生成的函數,就接收到了子組件傳入的數據啦

因此 做用域Slot 就能夠拿傳入的參數進行解析了


插槽怎麼插入子組件

_t('default',null,{ child:child }) 執行完畢,會返回節點,這個節點就是 slot 解析生成的節點

[ "我是放在組件的 slot :"+ {child:11} ]

子組件渲染函數執行完畢,生成的vnode 以下

{    
    tag:'main',    
    children:[
        "我在子組件裏面",
        "我是放在組件的 slot : {child:11}"
    ]
}

做用域插槽,成功地替換了原來的佔位符 <slot>,當上了正宮娘娘

最後,來張圖看下總的流程

image

公衆號

相關文章
相關標籤/搜索