Vue源碼解析-瞭解vue插槽slot篇

1.從簡單的匿名插槽開始

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函數簡化下,其它狀況先不看,這裏會把namefallback傳進來, 這裏先說一下name屬性,咱們上面定義是bash

Vue.component('button-counter', {
  template: '<div><slot>我是默認內容</slot></div>'
})
複製代碼

這裏slot沒有給propsname的值,因此默認是default,咱們能夠給它一個name值,例如app

Vue.component('button-counter', {
  template: '<div><slot name="header">我是默認內容</slot></div>'
})
複製代碼

那麼使用時函數

new Vue({
  el: '#app',
  template: '<button-counter><span slot="header">我是slot傳入內容</span></button-counter>'
})
複製代碼

可是上面的用法slot在2.6.0版本已經廢棄,提供了新的v-slot代替,可是你仍然能夠這麼寫,源碼中依然保存對slot的兼容處理,咱們看下用v-slot的寫法以及須要注意的地方ui

new Vue({
  el: '#app',
  template: '<button-counter><template v-slot:header><span>我是slot傳入內容</span></template></button-counter>'
})
複製代碼

注意這裏v-slot須要使用在template,不可再跟上面同樣直接做用於span標籤上面this

回到上面說的renderSlot函數,name這裏是默認值default,第二個參數fallback就是咱們在組件中的slot節點的默認值spa

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就是把childrenname相同的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地址

相關文章
相關標籤/搜索