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?
- 現在使用slot-scope將是一個巨大的變化,這意味着將永遠無法在2.x中發佈它。
- 即使在3.x中對其進行了更改,更改現有語法的語義也可能導致很多學習者感到困惑,因爲他們將Google視爲過時的學習材料。因此,必須引入一些新的東西以區別於slot-scope。
- 在3.x中,計劃統一插槽類型,因此不再需要(從概念上)區分作用域插槽和非作用域插槽。一個插槽可能會或可能不會接收props,但它們全都是插槽。有了這個概念上的統一,似乎沒有必要將slot和slot-scope作爲兩個特殊屬性,並且最好在單個結構下統一語法。
三、插槽的使用
- 單個插槽 (default slot)
- 具名插槽 (named slots)
- 作用域插槽 (scoped slot)
- 默認插槽(單個插槽/匿名插槽)
描述: 默認插槽就是指沒有名字的插槽,子組件未定義名字的插槽,父級將會把未指定插槽的填充內容填充到默認插槽中。默認插槽可以放置在組件的任意位置,但是一個組件中只能有一個該類插槽。
示例代碼如下:
子組件代碼定義了一個默認插槽:
<template>
<div>
<slot></slot>
</div>
</template>
父組件給默認插槽填充內容:
<template>
<div>
<child>
<template>
<h1>默認插槽內容</h1>
</template>
</child>
</div>
</template>
注意:
- 父級的填充內容如果指定到子組件沒有對應名字的插槽,那麼該內容不會被填充到默認插槽中。
- 如果子組件沒有默認插槽,而父級的填充內容指定到默認插槽中,那麼該內容就不會填充到子組件的任何一個插槽中。
- 如果子組件有多個默認插槽,而父組件所有指定到默認插槽的填充內容,將會全都填充到子組件的每個默認插槽中。
- 具名插槽
描述:具名插槽其實就是給插槽取個名字。一個子組件可以放多個插槽,且可以放在不同的地方,而父組件填充內容時,可以根據這個名字把內容填充到對應插槽中。
例如
子組件的代碼,設置了兩個插槽(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>
注意:
-
父級的填充內容如果指定到子組件的沒有對應名字插槽,那麼該內容不會被填充到默認插槽中。
-
如果子組件沒有默認插槽,而父級的填充內容指定到默認插槽中,那麼該內容就不會填充到子組件的任何一個插槽中。
- 作用域插槽
描述:作用域插槽其實就是帶數據的插槽,即帶參數的插槽,簡單的來說就是子組件提供給父組件的參數,該參數僅限於插槽中使用,父組件可根據子組件傳過來的插槽數據來進行不同的方式展現和填充插槽內容。作用域插槽要求在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 的關係
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多次調用同一個具名插槽
從輸出可以看出,新式寫法多次調用同一個具名插槽,後面的會覆蓋前面的,這個和老是寫法一個比較大的區別,在升級的時候就要特別注意。
經過上面的實踐對比,有以下幾點結論:
- v2.6.0以後在
$slots
裏可以取到具名key,都可以通過$scopedSlots
訪問到; - v2.6.0以後
v-slot
的寫法不能多次使用同名的具名插槽,後面的會覆蓋前面的,不會顯示兩個同名具名插槽的內容; - 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 版本後對 slot
和 slot-scope
做了一次統一的整合,讓它們全部都變爲函數的形式,所有的插槽都可以在 this.$scopedSlots
上直接訪問,這讓我們在開發高級組件的時候變得更加方便。
在 2.5 的版本中,由於生成 slot
的作用域是在父組件中,所以明明是子組件的插槽 slot
的更新是會帶着父組件一起更新的;在2.6中 scoped slot 在編譯時生成的是一個函數,這個函數被傳入子組件之後會在子組件的渲染函數中被調用。這意味着 scoped slot 的依賴會被子組件收集,那麼當依賴變動時就只會直接觸發子組件更新了,儘可能避免不必要的渲染。