寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】node
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧數組
今天探索Slot的另外一部分,做用域插槽。學習
首先,設置一個模板例子this
把子組件的 child 傳給 插槽prototype
父組件會解析成下面的渲染函數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
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 上,用於傳給插槽
執行子組件解析成的渲染函數以下
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>,當上了正宮娘娘
最後,來張圖看下總的流程