用途
將每個功能都包成component時,很方便可以直接拿來重複使用。
但有時總會有一些想要一些不同,總不能再額外寫一個很像的component,這樣就失去了component的重複使用性了。
這時可以透過<slot>標籤,component裡面放一些自定義的文字或內容。
slot標籤可以想像成是在component裡面挖了一個洞,將你要的內容塞進去。
這邊有兩種挖洞的方式,分別是單個插槽(Single Slot)與具名插槽(Named Slots)。
先來看一個沒有使用slot的範例
HTML部分:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | <h2>沒有插槽可替換的狀態</h2><no-slot-component>
 <p>我會被替換掉</p>
 </no-slot-component>
 
 
 <script type="text/x-template" id="noSlotComponent">
 <div class="alert alert-warning">
 <h6>我是一個元件</h6>
 <p>
 這沒有插槽。
 </p>
 </div>
 </script>
 
 | 
Javascript部分:
| 12
 3
 
 | Vue.component('no-slot-component', {template: '#noSlotComponent',
 });
 
 | 
結果如圖,只有呈現template裡面原本的內容 “這沒有插槽”。
而”替換的內容”卻沒有出現。

這是為什麼呢?
還記得生命週期中有個template的橋段
如果有template就會執行template 沒有就innerHTML
因此,當宣告component時有指定template的話,
在使用show me的時候,瀏覽器會去讀取指定template裡的內容,所以說直接寫在component中間的東西是不會被呈現的。
但有個例外,那就是使用slot 標籤。
單個插槽(Single Slot)
在template中加入<slot></slot>。
HTML部分:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | <h2>Slot 基礎範例</h2><h3>以外部資料取代slot</h3>
 <single-slot-component>
 <p>使用這段取代原本的 Slot。</p>
 </single-slot-component>
 
 <script type="text/x-template" id="singleSlotComponent">
 <div class="alert alert-warning">
 <h6>我是一個元件</h6>
 <slot>如果沒有內容,則會顯示此段落。</slot>
 </div>
 </script>
 
 | 
Javascript部分:
| 12
 3
 
 | Vue.component('single-slot-component', {template: '#singleSlotComponent',
 });
 
 | 
結果如圖,應該出現”如果沒有內容,則會顯示此段落。”
被取代為”使用這段取代原本的 Slot。”

在上面的範例如果在component裡沒有放其他內容,如下:
HTML部分:
| 12
 3
 
 | <h3>使用compoment slot裡default資料</h3><single-slot-component>
 </single-slot-component>
 
 | 
結果如圖,會直接採用component裡slot的default資料

具名插槽(Named Slots)
這時問題來了,如果想要在一個component裡挖很多洞放很多個slot呢?
這時就會需要幫每一個slot命名。
HTML部分:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 | <h2>具名插槽</h2><named-slot-component>
 <header slot="header">替換的 Header</header>
 <template slot="footer">替換的 Footer</template>
 <template slot="btn">按鈕內容</template>
 <p slot="content">其餘的內容</p>
 </named-slot-component>
 
 
 
 <script type="text/x-template" id="namedSlotComponent">
 <div class="card my-3">
 <div class="card-header">
 <slot name="header">這段是預設的文字</slot>
 </div>
 <div class="card-body">
 <slot name="content">
 <h5 class="card-title">Special title treatment</h5>
 <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
 </slot>
 <a href="#" class="btn btn-primary">
 <slot name="btn">spanGo somewhere</slot>
 </a>
 </div>
 <div class="card-footer">
 <slot name="footer">這是預設的 Footer</slot>
 </div>
 </div>
 </script>
 
 | 
Javascript部分:
| 12
 3
 
 | Vue.component('named-slot-component', {template: '#namedSlotComponent',
 });
 
 | 
結果如圖,內容有順利被替換掉。

不過可以注意到的是,當有name的時候,畫面顯示的順序會依照
template內所定義的位置呈現,也就是說在引用component時裡面寫的順序調換不受影響,如下:
HTML部分:
| 12
 3
 4
 5
 6
 7
 
 | <h2>具名插槽</h2><named-slot-component>
 <template slot="footer">替換的 Footer</template>
 <template slot="btn">按鈕內容</template>
 <p slot="content">其餘的內容</p>
 <header slot="header">替換的 Header</header>
 </named-slot-component>
 
 | 
以上範例的完整程式碼
v-slot寫法
在vue2.6版本時slot出了新的寫法v-slot
我這邊試寫了一下,範例可以參考v-slot寫法的程式碼
裡面有幾個點需要注意一下
- v-slot 只能添加到 <template>或自定義的component上,像是<div>或<p>就會錯誤。舊的寫法就沒有這個限制。
- v-slot的缩写是#
- 但建議若要使用v-slot就從頭到尾都用v-slot,若要用#就從頭到尾都用#
- 如果要使用v-slot要確認使用的vue的版本是否有支援,版本需2.6以上
編譯作用域(Compilation Scope)
編譯作用域是指編譯層級以所在範圍為主,也就是說父層的data由父層的data決定,子層的data由子層的data決定。
這邊用來兩個例子來說明
第一個範例:沒有使用slot
範例完整程式碼
HTML部分
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | <div id="app"><div class="parent">{{ msg }}</div>
 <child>{{ msg }}</child>
 </div>
 
 <template id="child-template">
 
 <div class="child">{{ msg }}</div>
 </template>
 
 | 
Javascript部分
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | Vue.component('child', {
 template: '#child-template',
 data: function () {
 return {
 msg: 'I\'m child.'
 };
 }
 });
 
 
 var parent = new Vue({
 el: '#app',
 data: {
 msg: 'I\'m parent.'
 }
 });
 
 | 
畫面呈現如下:

會這樣呈現的原因有兩個
- 剛剛提到生命週期的部分,在沒有使用slot的狀況下會直接讀取template裡的內容當模板渲染,因此<child></child>會被無視,只呈現<div class="child"></div>的內容
- 由於編譯作用域的原因,會讀取所在的component的data,所以<div class="child"></div>裡的msg是讀取子component的data
第二個範例:有使用slot
範例完整程式碼
HTML部分
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | <div id="app"><div class="parent">{{ msg }}</div>
 
 <child>
 <div>This "msg" is from parent: {{ msg }}</div>
 </child>
 </div>
 
 <template id="child-template">
 <div class="child">
 <div>{{ msg }}</div>
 <slot> Default Slot Content </slot>
 </div>
 </template>
 
 | 
畫面呈現如下:

會這樣呈現的原因
- 根據編譯作用域的原因,template中的第一個msg,會讀取子component的data
- 因為有使用slot,所以<slot><slot>中會呈現<div>This "msg" is from parent: </div>,由於<div>This "msg" is from parent: </div>所在位置為根實體,所以會取得根實體data中的msg呈現
參考資料