0%

vue 插槽 slot

用途

將每個功能都包成component時,很方便可以直接拿來重複使用。
但有時總會有一些想要一些不同,總不能再額外寫一個很像的component,這樣就失去了component的重複使用性了。
這時可以透過<slot>標籤,component裡面放一些自定義的文字或內容。
slot標籤可以想像成是在component裡面挖了一個洞,將你要的內容塞進去。

這邊有兩種挖洞的方式,分別是單個插槽(Single Slot)與具名插槽(Named Slots)。

先來看一個沒有使用slot的範例
HTML部分:

1
2
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部分:

1
2
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部分:

1
2
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部分:

1
2
3
Vue.component('single-slot-component', {
template: '#singleSlotComponent',
});

結果如圖,應該出現”如果沒有內容,則會顯示此段落。”
被取代為”使用這段取代原本的 Slot。”

在上面的範例如果在component裡沒有放其他內容,如下:
HTML部分:

1
2
3
<h3>使用compoment slot裡default資料</h3>
<single-slot-component>
</single-slot-component>

結果如圖,會直接採用component裡slot的default資料

具名插槽(Named Slots)

這時問題來了,如果想要在一個component裡挖很多洞放很多個slot呢?
這時就會需要幫每一個slot命名。
HTML部分:

1
2
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部分:

1
2
3
Vue.component('named-slot-component', {
template: '#namedSlotComponent',
});

結果如圖,內容有順利被替換掉。

不過可以注意到的是,當有name的時候,畫面顯示的順序會依照
template內所定義的位置呈現,也就是說在引用component時裡面寫的順序調換不受影響,如下:
HTML部分:

1
2
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部分

1
2
3
4
5
6
7
8
9
<div id="app">
<div class="parent">{{ msg }}</div>
<child>{{ msg }}</child>
</div>

<template id="child-template">
<!-- ok -->
<div class="child">{{ msg }}</div>
</template>

Javascript部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// children component
Vue.component('child', {
template: '#child-template',
data: function () {
return {
msg: 'I\'m child.'
};
}
});

// parent component
var parent = new Vue({
el: '#app',
data: {
msg: 'I\'m parent.'
}
});

畫面呈現如下:

會這樣呈現的原因有兩個

  1. 剛剛提到生命週期的部分,在沒有使用slot的狀況下會直接讀取template裡的內容當模板渲染,因此<child></child>會被無視,只呈現<div class="child"></div>的內容
  2. 由於編譯作用域的原因,會讀取所在的component的data,所以<div class="child"></div>裡的msg是讀取子component的data

第二個範例:有使用slot
範例完整程式碼

HTML部分

1
2
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>

畫面呈現如下:

會這樣呈現的原因

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

參考資料