# 插槽
> 該頁面假設你已經閱讀過了[組件基礎](https://cn.vuejs.org/v2/guide/components.html)。如果你還對組件不太了解,推薦你先閱讀它。
> 在 2.6.0 中,我們為具名插槽和作用域插槽引入了一個新的統一的語法 (即`v-slot`指令)。它取代了`slot`和`slot-scope`這兩個目前已被廢棄但未被移除且仍在[文檔中](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%BA%9F%E5%BC%83%E4%BA%86%E7%9A%84%E8%AF%AD%E6%B3%95)的特性。新語法的由來可查閱這份[RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md)。
## [插槽內容](https://cn.vuejs.org/v2/guide/components-slots.html#%E6%8F%92%E6%A7%BD%E5%86%85%E5%AE%B9 "插槽內容")
Vue 實現了一套內容分發的 API,這套 API 的設計靈感源自[Web Components 規范草案](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md),將`<slot>`元素作為承載分發內容的出口。
它允許你像這樣合成組件:
~~~
<navigation-link url="/profile">
Your Profile
</navigation-link>
~~~
然后你在`<navigation-link>`的模板中可能會寫為:
~~~
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
~~~
當組件渲染的時候,`<slot></slot>`將會被替換為“Your Profile”。插槽內可以包含任何模板代碼,包括 HTML:
~~~
<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>`元素,則該組件起始標簽和結束標簽之間的任何內容都會被拋棄。
## [編譯作用域](https://cn.vuejs.org/v2/guide/components-slots.html#%E7%BC%96%E8%AF%91%E4%BD%9C%E7%94%A8%E5%9F%9F "編譯作用域")
當你想在一個插槽中使用數據時,例如:
~~~
<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>
~~~
作為一條規則,請記住:
> 父級模板里的所有內容都是在父級作用域中編譯的;子模板里的所有內容都是在子作用域中編譯的。
## [后備內容](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%90%8E%E5%A4%87%E5%86%85%E5%AE%B9 "后備內容")
有時為一個插槽設置具體的后備 (也就是默認的) 內容是很有用的,它只會在沒有提供內容的時候被渲染。例如在一個`<submit-button>`組件中:
~~~
<button type="submit">
<slot></slot>
</button>
~~~
我們可能希望這個`<button>`內絕大多數情況下都渲染文本“Submit”。為了將“Submit”作為后備內容,我們可以將它放在`<slot>`標簽內:
~~~
<button type="submit">
<slot>Submit</slot>
</button>
~~~
現在當我在一個父級組件中使用`<submit-button>`并且不提供任何插槽內容時:
~~~
<submit-button></submit-button>
~~~
后備內容“Submit”將會被渲染:
~~~
<button type="submit">
Submit
</button>
~~~
但是如果我們提供內容:
~~~
<submit-button>
Save
</submit-button>
~~~
則這個提供的內容將會被渲染從而取代后備內容:
~~~
<button type="submit">
Save
</button>
~~~
## [具名插槽](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD "具名插槽")
> 自 2.6.0 起有所更新。已廢棄的使用`slot`特性的語法在[這里](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%BA%9F%E5%BC%83%E4%BA%86%E7%9A%84%E8%AF%AD%E6%B3%95)。
有時我們需要多個插槽。例如對于一個帶有如下模板的`<base-layout>`組件:
~~~
<div class="container">
<header>
<!-- 我們希望把頁頭放這里 -->
</header>
<main>
<!-- 我們希望把主要內容放這里 -->
</main>
<footer>
<!-- 我們希望把頁腳放這里 -->
</footer>
</div>
~~~
對于這樣的情況,`<slot>`元素有一個特殊的特性:`name`。這個特性可以用來定義額外的插槽:
~~~
<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>
~~~
現在`<template>`元素中的所有內容都將會被傳入相應的插槽。任何沒有被包裹在帶有`v-slot`的`<template>`中的內容都會被視為默認插槽的內容。
然而,如果你希望更明確一些,仍然可以在一個`<template>`中包裹默認插槽的內容:
~~~
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
~~~
任何一種寫法都會渲染出:
~~~
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
~~~
注意**`v-slot`只能添加在一個`<template>`上**(只有[一種例外情況](https://cn.vuejs.org/v2/guide/components-slots.html#%E7%8B%AC%E5%8D%A0%E9%BB%98%E8%AE%A4%E6%8F%92%E6%A7%BD%E7%9A%84%E7%BC%A9%E5%86%99%E8%AF%AD%E6%B3%95)),這一點和已經廢棄的[`slot`特性](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%BA%9F%E5%BC%83%E4%BA%86%E7%9A%84%E8%AF%AD%E6%B3%95)不同。
## [作用域插槽](https://cn.vuejs.org/v2/guide/components-slots.html#%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD "作用域插槽")
> 自 2.6.0 起有所更新。已廢棄的使用`slot-scope`特性的語法在[這里](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%BA%9F%E5%BC%83%E4%BA%86%E7%9A%84%E8%AF%AD%E6%B3%95)。
有時讓插槽內容能夠訪問子組件中才有的數據是很有用的。例如,設想一個帶有如下模板的`<current-user>`組件:
~~~
<span>
<slot>{{ user.lastName }}</slot>
</span>
~~~
我們想讓它的后備內容顯示用戶的名,以取代正常情況下用戶的姓,如下:
~~~
<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.user.firstName }}
</template>
</current-user>
~~~
在這個例子中,我們選擇將包含所有插槽 prop 的對象命名為`slotProps`,但你也可以使用任意你喜歡的名字。
### [獨占默認插槽的縮寫語法](https://cn.vuejs.org/v2/guide/components-slots.html#%E7%8B%AC%E5%8D%A0%E9%BB%98%E8%AE%A4%E6%8F%92%E6%A7%BD%E7%9A%84%E7%BC%A9%E5%86%99%E8%AF%AD%E6%B3%95 "獨占默認插槽的縮寫語法")
在上述情況下,當被提供的內容*只有*默認插槽時,組件的標簽才可以被當作插槽的模板來使用。這樣我們就可以把`v-slot`直接用在組件上:
~~~
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
~~~
這種寫法還可以更簡單。就像假定未指明的內容對應默認插槽一樣,不帶參數的`v-slot`被假定對應默認插槽:
~~~
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
~~~
注意默認插槽的縮寫語法**不能**和具名插槽混用,因為它會導致作用域不明確:
~~~
<!-- 無效,會導致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
~~~
只要出現多個插槽,請始終為*所有的*插槽使用完整的基于`<template>`的語法:
~~~
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
~~~
### [解構插槽 Prop](https://cn.vuejs.org/v2/guide/components-slots.html#%E8%A7%A3%E6%9E%84%E6%8F%92%E6%A7%BD-Prop "解構插槽 Prop")
作用域插槽的內部工作原理是將你的插槽內容包括在一個傳入單個參數的函數里:
~~~
function (slotProps) {
// 插槽內容
}
~~~
這意味著`v-slot`的值實際上可以是任何能夠作為函數定義中的參數的 JavaScript 表達式。所以在支持的環境下 ([單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html)或[現代瀏覽器](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9)),你也可以使用[ES2015 解構](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E8%A7%A3%E6%9E%84%E5%AF%B9%E8%B1%A1)來傳入具體的插槽 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>
~~~
## [動態插槽名](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%8A%A8%E6%80%81%E6%8F%92%E6%A7%BD%E5%90%8D "動態插槽名")
> 2.6.0 新增
[動態指令參數](https://cn.vuejs.org/v2/guide/syntax.html#%E5%8A%A8%E6%80%81%E5%8F%82%E6%95%B0)也可以用在`v-slot`上,來定義動態的插槽名:
~~~
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
~~~
## [具名插槽的縮寫](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD%E7%9A%84%E7%BC%A9%E5%86%99 "具名插槽的縮寫")
> 2.6.0 新增
跟`v-on`和`v-bind`一樣,`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>
~~~
## [其它示例](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%85%B6%E5%AE%83%E7%A4%BA%E4%BE%8B "其它示例")
**插槽 prop 允許我們將插槽轉換為可復用的模板,這些模板可以基于輸入的 prop 渲染出不同的內容。**這在設計封裝數據邏輯同時允許父級組件自定義部分布局的可復用組件時是最有用的。
例如,我們要實現一個`<todo-list>`組件,它是一個列表且包含布局和過濾邏輯:
~~~
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
~~~
我們可以將每個 todo 作為父級組件的插槽,以此通過父級組件對其進行控制,然后將`todo`作為一個插槽 prop 進行綁定:
~~~
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
<!--
我們為每個 todo 準備了一個插槽,
將 `todo` 對象作為一個插槽的 prop 傳入。
-->
<slot name="todo" v-bind:todo="todo">
<!-- 后備內容 -->
{{ todo.text }}
</slot>
</li>
</ul>
~~~
現在當我們使用`<todo-list>`組件的時候,我們可以選擇為 todo 定義一個不一樣的`<template>`作為替代方案,并且可以從子組件獲取數據:
~~~
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">?</span>
{{ todo.text }}
</template>
</todo-list>
~~~
這只是作用域插槽用武之地的冰山一角。想了解更多現實生活中的作用域插槽的用法,我們推薦瀏覽諸如[Vue Virtual Scroller](https://github.com/Akryum/vue-virtual-scroller)、[Vue Promised](https://github.com/posva/vue-promised)和[Portal Vue](https://github.com/LinusBorg/portal-vue)等庫。
## [廢棄了的語法](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%BA%9F%E5%BC%83%E4%BA%86%E7%9A%84%E8%AF%AD%E6%B3%95 "廢棄了的語法")
> `v-slot`指令自 Vue 2.6.0 起被引入,提供更好的支持`slot`和`slot-scope`特性的 API 替代方案。`v-slot`完整的由來參見這份[RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md)。在接下來所有的 2.x 版本中`slot`和`slot-scope`特性仍會被支持,但已經被官方廢棄且不會出現在 Vue 3 中。
### [帶有`slot`特性的具名插槽](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%B8%A6%E6%9C%89-slot-%E7%89%B9%E6%80%A7%E7%9A%84%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD "帶有 slot 特性的具名插槽")
> 自 2.6.0 起被廢棄。新推薦的語法請查閱[這里](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD)。
在`<template>`上使用特殊的`slot`特性,可以將內容從父級傳給具名插槽 (把[這里](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD)提到過的`<base-layout>`組件作為示例):
~~~
<base-layout>
<template 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 slot="footer">
<p>Here's some contact info</p>
</template>
</base-layout>
~~~
或者直接把`slot`特性用在一個普通元素上:
~~~
<base-layout>
<h1 slot="header">Here might be a page title</h1>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</base-layout>
~~~
這里其實還有一個未命名插槽,也就是**默認插槽**,捕獲所有未被匹配的內容。上述兩個示例的 HTML 渲染結果均為:
~~~
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
~~~
### [帶有`slot-scope`特性的作用域插槽](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%B8%A6%E6%9C%89-slot-scope-%E7%89%B9%E6%80%A7%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD "帶有 slot-scope 特性的作用域插槽")
> 自 2.6.0 起被廢棄。新推薦的語法請查閱[這里](https://cn.vuejs.org/v2/guide/components-slots.html#%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD)。
在`<template>`上使用特殊的`slot-scope`特性,可以接收傳遞給插槽的 prop (把[這里](https://cn.vuejs.org/v2/guide/components-slots.html#%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD)提到過的`<slot-example>`組件作為示例):
~~~
<slot-example>
<template slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
~~~
這里的`slot-scope`聲明了被接收的 prop 對象會作為`slotProps`變量存在于`<template`\> 作用域中。你可以像命名 JavaScript 函數參數一樣隨意命名`slotProps`。
這里的`slot="default"`可以被忽略為隱性寫法:
~~~
<slot-example>
<template slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
~~~
`slot-scope`特性也可以直接用于非`<template>`元素 (包括組件):
~~~
<slot-example>
<span slot-scope="slotProps">
{{ slotProps.msg }}
</span>
</slot-example>
~~~
`slot-scope`的值可以接收任何有效的可以出現在函數定義的參數位置上的 JavaScript 表達式。這意味著在支持的環境下 ([單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html)或[現代瀏覽器](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9)),你也可以在表達式中使用[ES2015 解構](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E8%A7%A3%E6%9E%84%E5%AF%B9%E8%B1%A1),如下:
~~~
<slot-example>
<span slot-scope="{ msg }">
{{ msg }}
</span>
</slot-example>
~~~
使用[這里](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%85%B6%E5%AE%83%E7%A4%BA%E4%BE%8B)描述過的`<todo-list>`作為示例,與它等價的使用`slot-scope`的代碼是:
~~~
<todo-list v-bind:todos="todos">
<template slot="todo" slot-scope="{ todo }">
<span v-if="todo.isComplete">?</span>
{{ todo.text }}
</template>
</todo-list>
~~~
- 內容介紹
- EcmaScript基礎
- 快速入門
- 常量與變量
- 字符串
- 函數的基本概念
- 條件判斷
- 數組
- 循環
- while循環
- for循環
- 函數基礎
- 對象
- 對象的方法
- 函數
- 變量作用域
- 箭頭函數
- 閉包
- 高階函數
- map/reduce
- filter
- sort
- Promise
- 基本對象
- Arguments 對象
- 剩余參數
- Map和Set
- Json基礎
- RegExp
- Date
- async
- callback
- promise基礎
- promise-api
- promise鏈
- async-await
- 項目實踐
- 標簽系統
- 遠程API請求
- 面向對象編程
- 創建對象
- 原型繼承
- 項目實踐
- Classes
- 構造函數
- extends
- static
- 項目實踐
- 模塊
- import
- export
- 項目實踐
- 第三方擴展庫
- immutable
- Vue快速入門
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入門
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 混入
- 過濾器
- 項目實踐
- 標簽編輯
- iView
- iView快速入門
- 課程講座
- 環境配置
- 第3周 Javascript快速入門