Vue.component('button-counter', { template: '<div> <slot>我是默認內容</slot></div>' })
new Vue({ el: '#app', template: '<button-counter><span>我是slot傳入內容</span></button-counter>' })
這裏先註冊一個button-counter
組件,而後使用它,上面渲染出來的結果是 我是slot內容node
原理解析:git
1.這裏直接看到組件button-counter
編譯後的render
函數,就不詳細從template
模板編譯提及,直接看最後的render函數:github
(function anonymous( ) { with(this){return _c('div',[_t("default",[_v("我是默認內容")])],2)} })
這裏_v
就是建立普通文本節點,主要看_t
函數,這裏_t
也就是renderSlot
函數的簡寫數組
function installRenderHelpers (target) { ... target._t = renderSlot; ... }
function renderSlot ( name, fallback, props, bindObject ) { var scopedSlotFn = this.$scopedSlots[name]; var nodes; nodes = scopedSlotFn(props) || fallback; return nodes; }
這裏把函數renderSlot
函數簡化下,其它狀況先不看,這裏會把name
和fallback
傳進來,
這裏先說一下name
屬性,咱們上面定義是app
Vue.component('button-counter', { template: '<div><slot>我是默認內容</slot></div>' })
這裏slot
沒有給props
爲name
的值,因此默認是default
,咱們能夠給它一個name
值,例如函數
Vue.component('button-counter', { template: '<div><slot name="header">我是默認內容</slot></div>' })
那麼使用時this
new Vue({ el: '#app', template: '<button-counter><span slot="header">我是slot傳入內容</span></button-counter>' })
可是上面的用法slot
在2.6.0版本已經廢棄,提供了新的v-slot
代替,可是你仍然能夠這麼寫,源碼中依然保存對slot的兼容處理,咱們看下用v-slot
的寫法以及須要注意的地方spa
new Vue({ el: '#app', template: '<button-counter><template v-slot:header><span>我是slot傳入內容</span></template></button-counter>' })
注意這裏v-slot
須要使用在template
,不可再跟上面同樣直接做用於span
標籤上面code
回到上面說的renderSlot
函數,name
這裏是默認值default
,第二個參數fallback
就是咱們在組件中的slot
節點的默認值component
var scopedSlotFn = this.$scopedSlots[name]; var nodes; nodes = scopedSlotFn(props) || fallback; return nodes;
若是this.$scopredSlots
存在該name
的值,則調用該返回函數生成一個nodes
節點返回,不然返回fallback
(即默認值),因此到這裏咱們知道爲何在自定義組件中有傳入子元素就渲染子元素,沒有就使用默認插槽裏面的值了,這裏涉及到this.$scopredSlots
這個變量,咱們接下來看下這個值,咱們首先看下vm.$slots
當組件在執行initRender
函數時
function initRender (vm) { ... vm.$slots = resolveSlots(options._renderChildren, renderContext); ... }
resolveSlots
函數會對children
節點作歸類和過濾處理,返回slots
function resolveSlots ( children, context ) { if (!children || !children.length) { return {} } var slots = {}; for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; var data = child.data; // remove slot attribute if the node is resolved as a Vue slot node if (data && data.attrs && data.attrs.slot) { delete data.attrs.slot; } // named slots should only be respected if the vnode was rendered in the // same context. if ((child.context === context || child.fnContext === context) && data && data.slot != null ) { // 若是slot存在(slot="header") 則拿對應的值做爲key var name = data.slot; var slot = (slots[name] || (slots[name] = [])); // 若是是tempalte元素 則把template的children添加進數組中,這也就是爲何你寫的template標籤並不會渲染成另外一個標籤到頁面 if (child.tag === 'template') { slot.push.apply(slot, child.children || []); } else { slot.push(child); } } else { // 若是沒有就默認是default (slots.default || (slots.default = [])).push(child); } } // ignore slots that contains only whitespace for (var name$1 in slots) { if (slots[name$1].every(isWhitespace)) { delete slots[name$1]; } } return slots }
這裏的_renderChildren
就是把children
的name
相同的VNodes
節點歸類放到一個數組中,最終返回了一個對象,key
爲對應的name
,值是包含該name
對應的節點數組
接下來的_render
函數的時候,經過normalizeScopedSlots
獲得vm.$scopedSlots
vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots );
有時咱們須要獲取子組件的一些內部數據,好比獲取下面msg
的值,可是當前執行的做用域是在父組件的實例上,經過this.msg
是獲取不到子組件裏面的值,咱們須要在<slot>
元素上動態綁定一個msg
對象屬性,而後在父組件能夠經過下面方式來獲取
Vue.component('button-counter', { data () { return { msg: 'hi' } }, template: '<div><slot :msg="msg">我是默認內容</slot></div>' })
new Vue({ el: '#app', template: '<button-counter><template scope="msg"><span>{{props.msg}}</span></template></button-counter>' })
這是2.5之前的寫法,2.5之後採用slot-scope
new Vue({ el: '#app', template: '<button-counter><template slot-scope="msg"><span>{{props.msg}}</span></template></button-counter>' })
2.6之後廢棄上面兩種寫法,採用v-slot
new Vue({ el: '#app', template: '<button-counter><template v-slot="msg"><span>{{props.msg}}</span></template></button-counter>' })
這裏可以拿到msg
是由於在renderSlot
的時候 執行會傳入props
,能夠看到編譯後的render
函數_t
的第三個參數就是props
(function anonymous( ) { with(this){return _c('div',[_t("default",[_v("我是默認的")],{"msg":msg})],2)} })
var scopedSlotFn = this.$scopedSlots[name]; var nodes; nodes = scopedSlotFn(props) || fallback; return nodes;
由render
函數能夠看到傳進去的props
後就可以拿到msg
的值了
(function anonymous( ) { with(this){return _c('button-counter',{scopedSlots:_u([{key:"default",fn:function(props){return [_c('span',[_v(_s(props.msg))])]}}])})} })
這裏只是簡單描述了幾個關鍵點,插槽做用域還有支持解構賦值,支持動態插槽名稱等寫法,還有不少的細節處理須要更加深刻去了解源碼。
若是以爲對你有幫忙,麻煩點個star哈
github地址