Vue 實現了一套內容分發的 API,這套 API 的設計靈感源自 Web Components 規範草案,將
<slot>
元素作為承載分發內容的出口。
例子(Vue 2.6.0 以上語法)
1. 插槽內容
子元件 <navigation-link>
<a v-bind:href="url" class="nav-link" >
<slot></slot>
</a>
複製程式碼
父元件
<navigation-link url="/profile">
Your Profile
</navigation-link>
複製程式碼
當元件渲染的時候,
<slot></slot>
將會被替換為“Your Profile”。
插槽內可以包含任何模板程式碼:
<navigation-link url="/profile">
<!-- 新增一個 Font Awesome 圖示 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>
複製程式碼
甚至其它的元件:
<navigation-link url="/profile">
<!-- 新增一個圖示的元件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
複製程式碼
如果
<navigation-link>
沒有包含一個<slot>
元素,則該元件起始標籤和結束標籤之間的任何內容都會被拋棄。
2. 預設內容(後備內容)
子元件 <submit-button>
<button type="submit">
<slot>Submit</slot> <!-- Submit 就是後備內容 -->
</button>
複製程式碼
父元件
- 當在一個父級元件中使用
<submit-button>
並且不提供任何插槽內容時,後備內容“Submit”將會被渲染:
<submit-button></submit-button>
複製程式碼
- 如果提供內容,則這個提供的內容將會被渲染從而取代後備內容:
<submit-button>Save</submit-button>
複製程式碼
3. 具名插槽
<slot>
元素有一個特殊的特性:name
。這個特性可以用來定義額外的插槽:
子元件 <base-layout>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
複製程式碼
一個不帶
name
的<slot>
出口會帶有隱含的名字“default”。
向具名插槽提供內容時,可以在一個 <template>
元素上使用 v-slot
指令,並以 v-slot
的引數的形式提供其名稱:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
複製程式碼
任何沒有被包裹在帶有
v-slot
的<template>
中的內容都會被視為預設插槽的內容。
注意: v-slot
只能新增在一個 <template>
上,只有一種情況例外(詳見“作用域插槽-獨佔預設插槽的縮寫語法”)。
4. 編譯作用域
當你想在一個插槽中使用資料時,例如第一個例子:
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
複製程式碼
該插槽跟模板(父元件中)的其它地方一樣可以訪問相同的例項屬性 (也就是相同的“作用域”),而不能訪問
<navigation-link>
的作用域。例如url
是訪問不到的:
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!-- 這裡的 `url` 會是 undefined,
因為"/profile"是傳遞給<navigation-link>的,只有在<navigation-link>內部可以訪問 -->
</navigation-link>
複製程式碼
父級模板裡的所有內容都是在父級作用域中編譯的;子模板裡的所有內容都是在子作用域中編譯的。
5. 作用域插槽(這裡我測試跟文件有點出入,具體看程式碼)
有時讓插槽內容能夠訪問子元件中才有的資料是很有用的。例如:
子元件 <current-user>
<span>
<slot>{{ user.lastName }}</slot>
</span>
<script>
data () {
return {
user: {
firstName: 'current-user-first-name',
lastName: 'current-user-last-name'
}
}
}
</script>
複製程式碼
父元件
<current-user>
{{ user.firstName }}
</current-user>
複製程式碼
然而上述程式碼不會正常工作,因為只有
<current-user>
元件可以訪問到user
而我們提供的內容是在父級渲染的。
為了讓
user
在父級的插槽內容可用,我們可以將user
作為一個<slot>
元素的特性繫結上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
複製程式碼
繫結在
<slot>
元素上的特性被稱為插槽 prop。現在在父級作用域中,我們可以給v-slot
帶一個值來定義我們提供的插槽 prop 的名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.firstName }}
</template>
</current-user>
複製程式碼
-
獨佔預設插槽的縮寫語法
在上述情況下,當被提供的內容只有預設插槽時,元件的標籤才可以被當作插槽的模板來使用。這樣我們就可以把
v-slot
直接用在元件上:
<current-user v-slot:default="slotProps">
{{ slotProps.firstName }}
</current-user>
複製程式碼
或
<current-user v-slot="slotProps">
{{ slotProps.firstName }}
</current-user>
複製程式碼
注意:
- 預設插槽的縮寫語法不能和具名插槽混用,因為它會導致作用域不明確:
<!-- 無效,會導致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
複製程式碼
- 只要出現多個插槽,請始終為所有的插槽使用完整的基於
<template>
的語法:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
複製程式碼
-
解構插槽 Prop
作用域插槽的內部工作原理是將你的插槽內容包括在一個傳入單個引數的函式裡:
function (slotProps) {
// 插槽內容
}
複製程式碼
這意味著
v-slot
的值實際上可以是任何能夠作為函式定義中的引數的 JavaScript 表示式。所以在支援的環境下 (單檔案元件或現代瀏覽器),你也可以使用 ES2015 解構來傳入具體的插槽 prop,如下:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
複製程式碼
這樣可以使模板更簡潔,尤其是在該插槽提供了多個 prop 的時候。它同樣開啟了 prop 重新命名等其它可能,例如將
user
重新命名為person
:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
複製程式碼
你甚至可以定義後備內容,用於插槽 prop 是
undefined
的情形:
<current-user v-slot="{ user = { firstName: 'Guest' } }">>
{{ user.firstName }}
</current-user>
複製程式碼
6. 動態插槽名
動態指令引數也可以用在
v-slot
上,來定義動態的插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
複製程式碼
7. 具名插槽的縮寫
v-slot
的縮寫,即把引數之前的所有內容 (v-slot:
) 替換為字元 #
。例如 v-slot:header
可以被重寫為 #header
:
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
複製程式碼
**注意:**和其它指令一樣,該縮寫只在其有引數的時候才可用:
<!-- 這樣會觸發一個警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
複製程式碼
如果你希望使用縮寫的話,你必須始終以明確插槽名取而代之:
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
複製程式碼
8. 其他示例
**插槽 prop 允許我們將插槽轉換為可複用的模板,這些模板可以基於輸入的 prop 渲染出不同的內容。**這在設計封裝資料邏輯同時允許父級元件自定義部分佈局的可複用元件時是最有用的。
例如,子元件 <todo-list>
:
<ul>
<li v-for="todo in filteredTodos" v-bind:key="todo.id">
<!-- 我們為每個 todo 準備了一個插槽, 將 `todo` 物件作為一個插槽的 prop 傳入。 -->
<slot v-bind:todo="todo">
<!-- 後備內容 -->
{{ todo.text }}
</slot>
</li>
</ul>
複製程式碼
父元件:
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
複製程式碼