# 插槽
> 本章节需要掌握组件基础
## 插槽内容
Vue实现一套优秀的内容分发接口,基于[WEB组件草案](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md),使用`<slot>`标签提供内容分发服务。
这允许你像这样构成一个组件:
~~~html
<todo-button>
Add todo
</todo-button>
~~~
`todo-button`模板应该这样写:
~~~html
<!-- todo-button 组件模板 -->
<button class="btn-primary">
<slot></slot>
</button>
~~~
组件被渲染时,`<slot></solt>`将会被替换为:Add todo:
~~~html
<!-- 渲染后的 HTML -->
<button class="btn-primary">
Add todo
</button>
~~~
插槽替换为字符仅仅是基本功能,它可以包含任何模板代码,如HTML代码:
~~~html
<todo-button>
<!-- 添加Font Awesome图标 -->
<i class="fas fa-plus"></i>
Add todo
</todo-button>
~~~
甚至其他组件:
~~~html
<todo-button>
<!-- 使用一个组件来加一个图标 -->
<font-awesome-icon name="plus"></font-awesome-icon>
Add todo
</todo-button>
~~~
如果组件模板没有任何插槽,提供任何内容都会被拒绝:
~~~html
<!-- todo-button 组件模板-->
<button class="btn-primary">
Create a new item
</button>
~~~
~~~html
<todo-button>
<!-- 下面文字不会被渲染 -->
Add todo
</todo-button>
~~~
## 渲染域
如果你想在组件内使用`data`,例如:
~~~html
<todo-button>
Delete a {{ item.name }}
</todo-button>
~~~
插槽有权限访问有共同实例属性(如相同域)的其余模板。

插槽没有权限访问`<todo-button>`域,下例中访问`action`将不会工作:
~~~html
<todo-button action="delete">
Clicking here will {{ action }} an item
<!--
`action`将会提示未定义, 因为这个内容来自于`<todo-button>`, 不会定义在`<todo-button>` 组件内。
-->
</todo-button>
~~~
记住这条规则:
>所有父模板都编译在父组件域,所有子模板都编译在子组件域。
## 备用内容
有些情形给插槽一个备用内容(如默认值)是比较有用的,当没有任何内容提供给插槽时,将会使用备用内容渲染。例如下面的`<submit-button>`组件:
~~~html
<button type="submit">
<slot></slot>
</button>
~~~
大部分时候我们都希望将button中的插槽渲染成`Submit`,将Submit设置为备用内容,我们可以将它放到`<slot></slot>`中间:
~~~html
<button type="submit">
<slot>Submit</slot>
</button>
~~~
当使用这个组件且不为插槽提供内容时:
~~~html
<submit-button></submit-button>
~~~
会被渲染为:
~~~html
<button type="submit">
Submit
</button>
~~~
但如果我们提供了内容:
~~~html
<submit-button>
Save
</submit-button>
~~~
渲染结果则为:
~~~
<button type="submit">
Save
</button>
~~~
## 具名插槽
使用多个插槽是经常用到的,如下面的`<base-layout>`组件模板:
~~~
<div class="container">
<header>
<!-- 想把头内容放在这里 -->
</header>
<main>
<!-- 想把主要内容放在这里 -->
</main>
<footer>
<!-- 想把脚内容放到这里 -->
</footer>
</div>
~~~
这种情形,插槽有一个特殊的属性`name`,它可以为每个插槽定义一个唯一的ID,这样你就可以决定哪些内容渲染到哪个地方了:
~~~html
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
~~~
没有指定`name`的插槽的名字是`default`。
模板中使用具名插槽,我们需要使用`v-slot`指令,`v-slot`参数就是插槽的名字:
~~~html
<base-layout>
<template v-slot:header>
<h1> 这里可能是标题</h1>
</template>
<template v-slot:default>
<p> 主要内容段落</p>
<p>另一段内容</p>
</template>
<template v-slot:footer>
<p>一些联系方式</p>
</template>
</base-layout>
~~~
现在所有模板内的元素都被传递到相应的插槽里,最终渲染结果可能如下:
~~~html
<div class="container">
<header>
<h1>这里可能是标题</h1>
</header>
<main>
<p> 主要内容段落</p>
<p>另一段内容</p>
</main>
<footer>
<p>一些联系方式</p>
</footer>
</div>
~~~
注意:`v-slot`只能放在`template`内(有一种[例外](https://v3.vuejs.org/guide/component-slots.html#abbreviated-syntax-for-lone-default-slots))
## 作用域插槽
有时,只有在子组件内部可以访问data内容是有用的。一个常用的例子就是使用组件渲染一个数组项,我们想定制数组项的渲染方式。
例如我们有一个组件,里面包含一个`todo-items`列表:
~~~
app.component('todo-list', {
data() {
return {
items: ['Feed a cat', 'Buy milk']
}
},
template: `
<ul>
<li v-for="(item, index) in items">
{{ item }}
</li>
</ul>
`
})
~~~
我们想把`{{ item }}`` 用一个插槽替换,以在父组件来定制:
~~~html
<todo-list>
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
~~~
但这不会工作,因为只有`<todo-list>`组件才能够访问`item`和父组件提供的插槽内容。
要想让父组件提供的插槽内容能够使用`item`,我们可以添加`<slot>`标签并绑定成属性:
~~~html
<ul>
<li v-for="( item, index ) in items">
<slot :item="item"></slot>
</li>
</ul>
~~~
一个插槽可以绑定多个属性:
~~~html
<ul>
<li v-for="( item, index ) in items">
<slot :item="item" :index="index" :another-attribute="anotherAttribute"></slot>
</li>
</ul>
~~~
属性绑定到`<slot>`叫做`slot props`。现在在父组件域,我们可以使用`v-slot`绑定值来定义一个slot props名字:
~~~html
<todo-list>
<template v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</template>
</todo-list>
~~~

在这个例子中,我们选择命名一个包含全部slot props的对象`slotProps`,你可以使用任何名称随你喜欢。
## 单默认插槽缩写语法
如上例,单个默认插槽,组件模板可以使用组件标签。这允许我们直接在组件上使用`v-slot`:
~~~html
<todo-list v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>
~~~
可以更简短,就像没有指定内容分配给默认插槽,无参数的`v-solt`指向默认插槽:
~~~html
<todo-list v-slot="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>
~~~
注意默认插槽的缩写语法不能混合具名插槽,这会导致作用域名不唯一:
~~~html
<!--不合法, 会出现告警 -->
<todo-list v-slot="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</todo-list>
~~~
无论何时有多个插槽时,需要为所有插槽使用完整的模板语法:
~~~html
<todo-list>
<template v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</todo-list>
~~~
## 解构插槽props
内部原理是,作用域插槽是插槽内含有一个参数的函数实现:
~~~
function (slotProps) {
// ... slot content ...
}
~~~
这意味着`v-slot`的值可以接受任何合法的JavaScript表达式,函数定义可以出现在参数位置。所以你可以使用[ES2005结构]()脱离特定的插槽props,像这样:
~~~html
<todo-list v-slot="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
~~~
这可以使用模板看起来更清爽,特别是插槽有多个props时。这也开启了其它可能性,比如重命名props,e.g. `item`转成`todo`:
~~~html
<todo-list v-slot="{ item: todo }">
<i class="fas fa-check"></i>
<span class="green">{{ todo }}</span>
</todo-list>
~~~
你甚至可以定义一个备用,在有些场景下插槽prop未定义时。
~~~html
<todo-list v-slot="{ item = 'Placeholder' }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
~~~
## 动态具名插槽
动态指令参数同样也适用于`v-slot`,允许你使用动态插槽名:
~~~html
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
~~~
## 具名插槽简写
同`v-on`(@),`v-bind`(:)一样,`v-slot`也可以简写,用`#`号替换参数前(`v-slot:`)的所有内容。例如,`v-slot:header` 可以重写为`#header`:
~~~html
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
~~~
但是,和其他指令一样,缩写仅适用于提供了参数的情形。这意味着下面的例子是不合法的:
~~~html
<!-- 这会触发一个警告 -->
<todo-list #="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
~~~
取而代之你必须为简写的插槽指定一个名字:
~~~html
<todo-list #default="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
~~~