vue slot-scope和v-slot

vue中插槽的使用

一、什麼是插槽

官方解釋:元素作爲組件模板之中的內容分發插槽,傳入內容後slot 元素自身將被替換。

插槽就是子組件中的提供給父組件使用的一個佔位符,用<slot></slot> 表示,父組件可以在這個佔位符中填充任何模板代碼,如 HTML、組件等,填充的內容會替換子組件的<slot></slot>標籤。

相當於組件的一塊HTML模板,這塊模板顯示不顯示、以及怎樣顯示由父組件來決定。

Slot 是 Vue組件的一個重要機制,它使得完全解耦的組件之間可以靈活地被組合。

二、插槽的迭代變遷

在2.5中,引入了slot-scope直接在slot元素上使用的功能,此時的slot-scope可以作用於任何標籤上,也可以作爲插槽使用在組件上。

<foo> 
  <bar slot-scope = "{msg}">
    {{msg}}
  </bar> 
</foo>

上面的用法導致了一個問題:slot-scope並不總是清楚地反映出哪個組件實際上提供了範圍變量。這裏slot-scope放置在bar組件上,但實際上是在定義默認插槽所提供的範圍變量foo,而且隨着嵌套加深,情況變得更糟

2.6版本爲 slot 引入了一套全新的模版語法v-slot

新語法將普通的插槽 (slot) 和作用域插槽 (scoped slot) 統一在一個指令語法下,並在整體上強調明確性 (explicitness) 和一致性 (consistency)。同時,由於新語法和舊語法完全兼容,這使得可以在 2.6 中發佈它。此時的v-slot只作用於<template>上;只有一種例外情況,當被提供的內容只有默認插槽時,組件的標籤纔可以被當作插槽的模板來使用,可以把 v-slot 直接用在組件上。

爲什麼要使用新指令而不是固定指令slot-scope?
  1. 現在使用slot-scope將是一個巨大的變化,這意味着將永遠無法在2.x中發佈它。
  2. 即使在3.x中對其進行了更改,更改現有語法的語義也可能導致很多學習者感到困惑,因爲他們將Google視爲過時的學習材料。因此,必須引入一些新的東西以區別於slot-scope。
  3. 在3.x中,計劃統一插槽類型,因此不再需要(從概念上)區分作用域插槽和非作用域插槽。一個插槽可能會或可能不會接收props,但它們全都是插槽。有了這個概念上的統一,似乎沒有必要將slot和slot-scope作爲兩個特殊屬性,並且最好在單個結構下統一語法。

三、插槽的使用

  • 單個插槽 (default slot)
  • 具名插槽 (named slots)
  • 作用域插槽 (scoped slot)
  1. 默認插槽(單個插槽/匿名插槽)
    描述: 默認插槽就是指沒有名字的插槽,子組件未定義名字的插槽,父級將會把未指定插槽的填充內容填充到默認插槽中。默認插槽可以放置在組件的任意位置,但是一個組件中只能有一個該類插槽
    示例代碼如下:
    子組件代碼定義了一個默認插槽:
<template>
    <div>
        <slot></slot>
    </div>
</template>

父組件給默認插槽填充內容:

<template>
    <div>
        <child>
            <template>
                <h1>默認插槽內容</h1>
            </template>
        </child>
    </div>
</template>

注意:

  • 父級的填充內容如果指定到子組件沒有對應名字的插槽,那麼該內容不會被填充到默認插槽中。
  • 如果子組件沒有默認插槽,而父級的填充內容指定到默認插槽中,那麼該內容就不會填充到子組件的任何一個插槽中。
  • 如果子組件有多個默認插槽,而父組件所有指定到默認插槽的填充內容,將會全都填充到子組件的每個默認插槽中。
  1. 具名插槽
    描述:具名插槽其實就是給插槽取個名字。一個子組件可以放多個插槽,且可以放在不同的地方,而父組件填充內容時,可以根據這個名字把內容填充到對應插槽中。
    例如
    子組件的代碼,設置了兩個插槽(header和footer):
<template>
    <div>
        <div>
            <slot name="header"></slot>
        </div>
        <div>
            <slot name="footer"></slot>
        </div>
    </div>
</template>

父組件填充內容, 父組件通過 v-slot:[name] 的方式指定到對應的插槽中,2.6.0 新增(v-slot:) 縮寫替換爲字符 #

<template>
    <div>
        <child>
            <template v-slot:header>
                <h1>頭部內容</h1>
            </template>
            <template #footer>
                <h1>尾部內容</h1>
            </template>
            //2.6.0版本之前的寫法
            <h1 slot="header">頭部內容</h1>
            <h1 slot="footer">尾部內容</h1>
        </child>
    </div>
</template>

注意:

  • 父級的填充內容如果指定到子組件的沒有對應名字插槽,那麼該內容不會被填充到默認插槽中。

  • 如果子組件沒有默認插槽,而父級的填充內容指定到默認插槽中,那麼該內容就不會填充到子組件的任何一個插槽中。

  1. 作用域插槽
    描述:作用域插槽其實就是帶數據的插槽,即帶參數的插槽,簡單的來說就是子組件提供給父組件的參數,該參數僅限於插槽中使用,父組件可根據子組件傳過來的插槽數據來進行不同的方式展現和填充插槽內容。作用域插槽要求在slot上面綁定數據
    例如下面子組件定義了一個top插槽:
<div class="child4">
     <slot name="top" :data="item"></slot>
</div>
<child4>
     <template #top="slotProps">
          <div>{{slotProps.data}}</div>
     </template>
  	 <template #top="{data}">   //解構插槽 
          <div>{{data}}</div>
     </template>
</child4>

//2.6.0版本之前的使用
<div class="child3">
     <slot name="top" :data="item"></slot>
</div>
<child3>
     <div slot="top" slot-scope="slotprop">{{slotprop.data}}</div>
</child3>

常用場景之一
如果子組件中的某一部分的數據,每個父組件都會有自己的一套對該數據的不同的呈現方式,這時就需要用到作用域插槽。

四、slot, slot-scope 以及 v-slot, v-slot:[name] = slotProps 與 $slots, $scopedSlots 的關係

slotsslots用來訪問被插槽分發的內容;scopedSlots用來訪問作用域插槽,對於包括 默認 slot 在內的每一個插槽,該對象都包含一個返回相應 VNode 的函數。

一般在應用中不涉及渲染函數,很少用得到$slots$scopedSlots,在使用渲染函數書寫一個組件時及在深入理解插槽的實現和二次封裝 UI庫 的時候就可能用得上了。

1. 無作用域插槽

<div id="app">
    <div id="parent-template">
    	<child>
        <!--2.5.11-->
        <p slot="header">header</p>
        <p slot="footer">footer</p>
        <p>default</p>
        <!--2.6.11-->
        <template v-slot:header>header</template>
        <template v-slot:footer>footer</template>
        <template>default</template>
      </child>
		</div>
</div>
<template id="child-template">
    <div>
        <slot name="header"></slot>
        <slot>默認分發處</slot>
        <slot name="footer"></slot>
    </div>
</template>
<script>
  // 註冊子組件
Vue.component("child", {
   template:"#child-template",
   mounted() {
      console.log('$slots',this.$slots)
      console.log('$scopedSlots',this.$scopedSlots)
   }
});
  // 初始化父組件
new Vue({
   el: "#parent-template"
});
</script>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mLp09Sd7-1588226114276)(./images/slot2.5.11.jpg)]

​ v2.5.11無作用域插槽.jpg

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4yMJ8s9w-1588226114318)(./images/slot2.6.11.jpg)]

​ v2.6.11無作用域插槽.jpg

從這個結果中可以看出v2.6.0以前,slot 和 slot-scope是分開的 ;

v2.6.0以前父組件沒有調用 slot-scope 時, $scopedSlots 裏面爲空 ;

v2.6.0以後 $scopedSlots 裏的具名值和 $slots 具名值是一一對應的。

2. 有作用域插槽

<div id="app">
    <div id="parent-template">
      <child>
        <!--此處是待分發的內容-->
        <!--2.5.11-->
        <p slot="header" slot-scope="{data}">{{data}}</p>
        <p slot="footer" slot-scope="scopedata">{{scopedata.data}}</p>
        <p>default</p>
        <!--2.6.11-->
        <template v-slot:header="headerdata">{{headerdata.data}}</template>
        <template v-slot:footer="footerdata">{{footerdata.data}}</template>
        <template v-slot:default>default</template>        
      </child>
    </div>
    <template id="child-template">
      <div>
        <slot name="header" :data="header"></slot>
        <slot>默認分發處</slot>
        <slot name="footer" :data="footer"></slot>
      </div>
    </template>
</div>
<script>
  // 註冊子組件
Vue.component("child", {
  template:"#child-template",
  data() {
    return {
      header: '我是頭部內容',
      footer: '我是底部內容'     
    }
  },
  mounted() {
    console.log('$slots',this.$slots)
    console.log('$scopedSlots',this.$scopedSlots)
  }
});
// 初始化父組件
new Vue({
    el: "#parent-template"
});
</script>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mtZGVuE6-1588226114334)(./images/scoped2.5.11.jpg)]

​ v2.5.11有作用域插槽.jpg

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Yee7yZDu-1588226114338)(./images/scoped2.6.11.jpg)]

​ v2.6.11有作用域插槽.jpg

v2.6.0以前,在父組件中’header’具名插槽中調用了slot-scope後,$scopedSlots 中出現了‘header’爲key的function, $slots 中‘header’爲key的VNode不見了;

v2.6.0以後,在父組件中’header’具名插槽中調用了slot-scope後,$scopedSlots 中沒有變化, $slots 中‘header’爲key的VNode不見了。一開始$slots裏面沒有值,是後面塞進去的

3. 多次調用同一個具名插槽

<div id="app">
    <div id="parent-template">
      <child>
        <template v-slot:header>
          <p>header</p>
        </template>
        <template v-slot:header>
          <p>header1</p>
        </template>
        <template v-slot:footer>
          <p>footer</p>
        </template>
        <template v-slot:default>
          <p>default</p>
        </template>
      </child>
    </div>
    <template id="child-template">
      <div>
        <slot name="header" :data="header"></slot>
        <slot>默認分發處</slot>
        <slot name="footer" :data="footer"></slot>
      </div>
    </template>
  </div>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uc3q5MKK-1588226114343)(./images/more-slot2.5.11.jpg)] v2.5.11多次調用同一個具名插槽

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-umr8LNfA-1588226114350)(./images/more-slot.jpg)]

​ v2.6.11多次調用同一個具名插槽

從輸出可以看出,新式寫法多次調用同一個具名插槽,後面的會覆蓋前面的,這個和老是寫法一個比較大的區別,在升級的時候就要特別注意。

經過上面的實踐對比,有以下幾點結論:

  1. v2.6.0以後在$slots裏可以取到具名key,都可以通過 $scopedSlots 訪問到;
  2. v2.6.0以後v-slot的寫法不能多次使用同名的具名插槽,後面的會覆蓋前面的,不會顯示兩個同名具名插槽的內容;
  3. v2.6.0以後用v-slot的寫法,如果在子組件中用$slot,一定調用watch方法,因爲一開始$slots爲空,後面才塞入的數據

總結

語法:

2.6版本之前 slot=[name] slot-scope="data"

2.6版本之後 v-slot:[name] v-slot:[name]="data" 雖然已經廢棄2.6之前的語法,但也支持不會報錯

在3.0中,最終刪除slot-scope,並且僅支持新語法v-slot

性能優化:

v2.6 版本後對 slotslot-scope 做了一次統一的整合,讓它們全部都變爲函數的形式,所有的插槽都可以在 this.$scopedSlots 上直接訪問,這讓我們在開發高級組件的時候變得更加方便。

在 2.5 的版本中,由於生成 slot 的作用域是在父組件中,所以明明是子組件的插槽 slot 的更新是會帶着父組件一起更新的;在2.6中 scoped slot 在編譯時生成的是一個函數,這個函數被傳入子組件之後會在子組件的渲染函數中被調用。這意味着 scoped slot 的依賴會被子組件收集,那麼當依賴變動時就只會直接觸發子組件更新了,儘可能避免不必要的渲染。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章