官网:https://cn.vuejs.org/guide/components/slots.html
默认内容
在外部没有提供任何内容的情况下,可以为插槽指定默认内容。
<!-- <SubmitButton> 组件 -->
<button type="submit">
<slot>
Submit <!-- 默认内容 -->
</slot>
</button>
当我们在父组件中使用 <SubmitButton>
且没有提供任何插槽内容时:
<SubmitButton />
<SubmitButton>Save</SubmitButton>
<!-- 效果 -->
<button type="submit">Submit</button>
<button type="submit">Save</button>
具名插槽
有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个 <BaseLayout>
组件中,有如下模板:
<div class="container">
<header>
<!-- 标题内容放这里 -->
</header>
<main>
<!-- 主要内容放这里 -->
</main>
<footer>
<!-- 底部内容放这里 -->
</footer>
</div>
对于这种场景,<slot>
元素可以有一个特殊的 attribute name
,用来给各个插槽分配唯一的 ID
,以确定每一处要渲染的内容:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
这类带 name
的插槽被称为具名插槽 (named slots)。没有提供 name
的 <slot>
出口会隐式地命名为“default”
。
要为具名插槽传入内容,我们需要使用一个含 v-slot
指令的 <template>
元素,并将目标插槽的名字传给该指令:
<BaseLayout>
<template v-slot:header>
<!-- header 插槽的内容放这里 -->
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<!-- 以上相当于 -->
<!-- 隐式的默认插槽 -->
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
v-slot
有对应的简写#
,因此<template v-slot:header>
可以简写为<template #header>
。其意思就是“将这部分模板片段传入子组件的header
插槽中”。
最终渲染出的 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
上也是有效的,即可以定义下面这样的动态插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
插槽的内容想要同时使用父组件域内和子组件域内的数据,如下:
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="这是text" :count="1"></slot>
</div>
<!-- 默认插槽接收props -->
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
<!-- 使用解构 -->
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
具名作用域插槽
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
<!-- <MyComponent> 的模板 -->
<slot name="header" message="hello"></slot>
注意:插槽上的
name
是一个 Vue 特别保留的attribute
,不会作为props
传递给插槽。因此最终headerProps
的结果是
{ message: 'hello' }
。
如果你同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 <template>
标签。尝试直接为组件添加 v-slot
指令将导致编译错误。这是为了避免因默认插槽的 props 的作用域而困惑。举例:
<!-- 该模板无法编译 -->
<template>
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<!-- message 属于默认插槽,此处不可用 -->
<p>{{ message }}</p>
</template>
</MyComponent>
</template>
为默认插槽使用显式的 <template>
标签有助于更清晰地指出 message
属性在其他插槽中不可用:
<template>
<MyComponent>
<!-- 使用显式的默认插槽 -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
</template>
高级列表组件示例
它会渲染一个列表,并同时会封装一些加载远端数据的逻辑、使用数据进行列表渲染、或者是像分页或无限滚动这样更进阶的功能。
<!-- 传入接口URL统一处理 -->
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p>by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>
在 <FancyList>
之中,我们可以多次渲染 <slot>
并每次都提供不同的数据 (注意我们这里使用了 v-bind
来传递插槽的 props
):
<ul>
<li v-for="item in items">
<slot name="item" v-bind="item"></slot>
</li>
</ul>