Close
升级到 Vue 3 | Vue 2 EOL

插槽

本页假设您已经阅读了 组件基础。如果您是组件新手,请先阅读它。

在 2.6.0 中,我们引入了一种新的统一语法 (v-slot 指令) 用于命名插槽和作用域插槽。它取代了 slotslot-scope 属性,这些属性现在已弃用,但尚未删除,并且仍然在 此处 文档化。引入新语法的理由在以下 RFC 中描述。

插槽内容

Vue 实现了一个内容分发 API,灵感来自 Web Components 规范草案,使用 <slot> 元素作为内容分发出口。

这允许您像这样组合组件

<navigation-link url="/profile">
Your Profile
</navigation-link>

然后在 <navigation-link> 的模板中,您可能会有

<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>

当组件渲染时,<slot></slot> 将被替换为“您的个人资料”。插槽可以包含任何模板代码,包括 HTML

<navigation-link url="/profile">
<!-- Add a Font Awesome icon -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>

甚至其他组件

<navigation-link url="/profile">
<!-- Use a component to add an icon -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>

如果 <navigation-link> 的模板没有包含 <slot> 元素,则其开始和结束标签之间提供的任何内容都将被丢弃。

编译范围

当您想在插槽中使用数据时,例如在

<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 }}
<!--
The `url` will be undefined, because this content is passed
_to_ <navigation-link>, rather than defined _inside_ the
<navigation-link> component.
-->
</navigation-link>

作为一项规则,请记住

父模板中的所有内容都在父范围中编译;子模板中的所有内容都在子范围中编译。

回退内容

在某些情况下,为插槽指定回退(即默认)内容很有用,只有在没有提供内容时才渲染。例如,在 <submit-button> 组件中

<button type="submit">
<slot></slot>
</button>

我们可能希望“提交”文本在大多数情况下渲染在 <button> 中。为了使“提交”成为回退内容,我们可以将其放置在 <slot> 标签之间

<button type="submit">
<slot>Submit</slot>
</button>

现在,当我们在父组件中使用 <submit-button> 时,不为插槽提供任何内容

<submit-button></submit-button>

将渲染回退内容“提交”

<button type="submit">
Submit
</button>

但是,如果我们提供内容

<submit-button>
Save
</submit-button>

那么将渲染提供的內容而不是回退内容

<button type="submit">
Save
</button>

命名插槽

在 2.6.0+ 中更新。 请参阅此处 以了解使用 slot 属性的弃用语法。

有时,拥有多个插槽很有用。例如,在具有以下模板的 <base-layout> 组件中

<div class="container">
<header>
<!-- We want header content here -->
</header>
<main>
<!-- We want main content here -->
</main>
<footer>
<!-- We want footer content here -->
</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>

无论哪种方式,渲染的 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>

请注意,v-slot 只能添加到 <template>有一个例外),与弃用的 slot 属性 不同。

作用域插槽

在 2.6.0+ 中更新。 请参阅此处 以了解使用 slot-scope 属性的弃用语法。

有时,让插槽内容访问仅在子组件中可用的数据很有用。例如,想象一个具有以下模板的 <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> 元素的属性称为插槽 props。现在,在父级范围中,我们可以使用带有值的 v-slot 来定义我们已提供的插槽 props 的名称

<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>

在此示例中,我们选择将包含所有插槽 props 的对象命名为 slotProps,但您可以使用任何您喜欢的名称。

仅默认插槽的简写语法

在上述情况下,当默认插槽提供内容时,组件的标签可以用作插槽的模板。这使我们能够直接在组件上使用 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>

请注意,默认插槽的简写语法不能与命名插槽混合使用,因为它会导致范围歧义

<!-- INVALID, will result in warning -->
<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>

解构插槽 Props

在内部,作用域插槽通过将您的插槽内容包装在一个传递单个参数的函数中来工作

function (slotProps) {
// ... slot content ...
}

这意味着 v-slot 的值实际上可以接受任何有效的 JavaScript 表达式,该表达式可以出现在函数定义的参数位置。因此,在支持的环境(单文件组件现代浏览器)中,您也可以使用 ES2015 解构 来提取特定的插槽 props,如下所示

<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>

这可以使模板更加简洁,尤其是在插槽提供许多 props 时。它还打开了其他可能性,例如重命名 props,例如将 user 重命名为 person

<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>

您甚至可以定义回退,在插槽 prop 未定义的情况下使用

<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>

动态插槽名称

在 2.6.0+ 中新增

动态指令参数 也适用于 v-slot,允许定义动态插槽名称

<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>

命名插槽简写

在 2.6.0+ 中新增

v-onv-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>

但是,就像其他指令一样,简写仅在提供参数时才可用。这意味着以下语法无效

<!-- This will trigger a warning -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>

相反,如果您希望使用简写,则必须始终指定插槽的名称

<current-user #default="{ user }">
{{ user.firstName }}
</current-user>

其他示例

插槽 props 使我们能够将插槽变成可重用的模板,这些模板可以根据输入 props 渲染不同的内容。 当您正在设计一个可重用组件来封装数据逻辑,同时允许使用它的父组件自定义其布局的一部分时,这最有用。

例如,我们正在实现一个<todo-list>组件,它包含列表的布局和过滤逻辑。

<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

与其为每个待办事项硬编码内容,我们可以让父组件通过将每个待办事项都设为一个插槽来控制,然后将todo绑定为插槽属性。

<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
<!--
We have a slot for each todo, passing it the
`todo` object as a slot prop.
-->
<slot name="todo" v-bind:todo="todo">
<!-- Fallback content -->
{{ todo.text }}
</slot>
</li>
</ul>

现在,当我们使用<todo-list>组件时,我们可以选择性地为待办事项定义一个替代的<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 ScrollerVue PromisedPortal Vue等库。

已弃用语法

v-slot指令是在 Vue 2.6.0 中引入的,它提供了一个改进的、替代的 API,用于仍然支持的slotslot-scope属性。引入v-slot的完整理由在RFC中进行了描述。slotslot-scope属性将在所有未来的 2.x 版本中继续得到支持,但它们已被正式弃用,最终将在 Vue 3 中删除。

使用slot属性的命名插槽

已弃用在 2.6.0+ 中。有关新的、推荐的语法,请参见此处

要从父组件传递内容到命名插槽,请在<template>上使用特殊的slot属性(使用此处描述的<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属性的作用域插槽

已弃用在 2.6.0+ 中。有关新的、推荐的语法,请参见此处

要接收传递到插槽的 props,父组件可以使用带有slot-scope属性的<template>(使用此处描述的<slot-example>作为示例)。

<slot-example>
<template slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>

此处,slot-scope将接收到的 props 对象声明为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 表达式,该表达式可以出现在函数定义的参数位置。这意味着在支持的环境(单文件组件现代浏览器)中,您也可以使用ES2015 解构在表达式中,如下所示。

<slot-example>
<span slot-scope="{ msg }">
{{ msg }}
</span>
</slot-example>

使用此处描述的<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>