不知道大家在開始使用元件的時候有沒有一個疑惑,明明元件在模版中的寫法跟一般的 HTML 標籤沒兩樣
<my-component></my-component>
但我們如果試著在裡面添加文字,卻不會有任何效果
<my-component>這句話寫在這裡不會呈現在畫面上</my-component>
原因其實是元件的內容,會是由定義元件時所寫的 template
決定,並不是由「元件標籤」的內容來定義,想想看要是元件的內容會在「使用元件」時定義,好像就失去了「拆元件」的本意了。
不過有時候如果能讓我們在使用元件的時候定義當下所需的資料,也是能讓元件的使用變得更彈性一些。
為此 Vue 提供了一個叫「插槽」的功能來達成這件事。
假設我們定義了一顆按鈕元件,我們可以在元件的 template 中使用一個特殊的標籤 <slot>
讓使用元件的時候,能將資料塞在裡面。
app.component("fancy-button", {
template: `
<button>
<slot></slot> <!-- 插槽 -->
</button>`,
});
定義好 <slot>
之後,使用元件時,就可以在元件標籤內定義「插槽」要放的內容:
<fancy-button>這是一顆重要的按鈕</fancy-button>
這就可以讓我們在每次使用元件時,定義元件的「部分」資料:
<slot>
標籤的預設值或許有些功能只有在特定情況會需要更改插槽資料,絕大多數的情況在插槽中要塞的資料都會是固定的,這種情況就很適合設定插槽的「預設值」。
設定方式很簡單,就是在元件插槽中把預設值寫好就好:
app.component("fancy-button", {
template: `
<button>
<slot>預設內容</slot> <!-- 插槽 -->
</button>`,
});
這麼一來在使用時,如果沒有寫內容的元件,就會以預設值處理了
<slot>
標籤設定名稱如果一個元件的用途是提供一個「架構」,而他的「內容」會是在使用元件時才決定,在上面的內容已經知道可以使用 <slot>
來處理。
不過要是元件的「內容」被拆了好幾個區塊,例如「header
」、「main
」,一個 slot 就不夠用了。
在定義多個 slot 時,必須針對每個 slot 命名,這樣使用時的內容才有依據能夠一一對應到對的插槽中。
插槽命名的方式,只要在<slot>
標籤中添加name
屬性即可。
app.component("dialog-box", {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot name="main"></slot>
</main>
</div>
`,
});
實際使用時,則會使用 <template>
標籤,加上v-slot:名稱
的指令來宣告每一個區塊的內容
<dialog-box>
<template v-slot:header>這是頭</template>
<template v-slot:main>這是內容</template>
</dialog-box>
範例程式提供於 jsfiddle
跟其他指令一樣,v-slot:
也有一個 #
作為簡寫,所以上面的內容也可以寫成下面這樣:
<dialog-box>
<template #header>這是頭</template>
<template #main>這是內容</template>
</dialog-box>
範例程式提供於 jsfiddle
剛剛的範例中都是使用「純文字」當作 slot 的內容,不過slot 的內容也可以定義 HTML 標籤:
<dialog-box>
<template #header>
<h1>這是標題</h1>
</template>
<template #main>
<p>這是內容</p>
</template>
</dialog-box>
範例程式提供於 jsfiddle
<slot>
標籤會成為預設的 slot如果我們在元件中使用多個 <slot>
標籤 ,有些 <slot>
標籤沒有命名的話,Vue 會把他當成預設的 slot
app.component("dialog-box", {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 他會被當成預設 slot-->
</main>
</div>
`,
});
如果要在模板中塞資料到這個 slot 中,可以用 default
的字樣來宣告:
<dialog-box>
<template #header>這是頭</template>
<template #default>這是內容</template>
</dialog-box>
不過還記得一開始沒對 <slot>
標籤命名的時候,我們是怎麼塞資料到 slot 的嗎?
對,是直接把資料寫在元件標籤內:
<fancy-button>這是一顆重要的按鈕</fancy-button>
換言之,如果我們直接在元件標籤寫資料,內容會直接塞到「預設 slot」裡面:
<dialog-box>
<template #header>
<h1>這是標題</h1>
</template>
<p>這是內容1</p>
<p>這是內容2</p>
<p>這是內容3</p>
<p>這是內容4</p>
</dialog-box>
範例程式提供於 jsfiddle
一般的元件操作方式,通常會是「元件定義結構」,從外部使用元件時,再把資料塞到元件中,讓元件渲染出資料。
不過如果透過插槽,Vue 有辦法做到在元件中定義資料,使用元件時再決定結構,做法是在元件的 <slot>
標籤定義「屬性」,就像 props
那樣。
這裡定義一個 text
的屬性,並且把元件的 msg
變數綁上去。
app.component("my-component", {
template: `
<div class="container">
<slot :text="msg"></slot>
</div>
`,
setup() {
const msg = Vue.ref("Hello World");
return {
msg,
};
},
});
在模版中,在 v-slot
定義一個名稱,即可以拿他取得 text
的資料
<my-component v-slot="slotProps">{{slotProps.text}}</my-component>
這個名稱不一定要叫 slotProps
,可以依當下需求定義。
範例程式提供於 jsfiddle