插槽 Slots
在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。通过使用插槽,可使组件更加灵活和具有可复用性。组件可以用在不同的地方渲染各异的内容,但同时还保证都具有相同的样式。
插槽内容与出口
<slot>
元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。

通过使用插槽,<FancyButton>
仅负责渲染外层的 <button>
(以及相应的样式),而其内部的内容由父组件提供。
插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:
template
<FancyButton>
<span style="color:red">Click me!</span>
<AwesomeIcon name="plus" />
</FancyButton>
通过使用插槽,<FancyButton>
组件更加灵活和具有可复用性。现在组件可以用在不同的地方渲染各异的内容,但同时还保证都具有相同的样式。
渲染作用域
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。
template
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>
这里的两个 {{ message }}
插值表达式渲染的内容都是一样的。
但插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。即:
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
默认内容
在外部没有提供任何内容的情况下,可以为插槽指定默认内容。
例如:
template
//子组件
<button type="submit">
<slot>
--Submit-- <!-- 默认内容 -->
</slot>
</button>
template
//父组件
<div>
<SubmitButton />
</div>
</button>
当父组件没有提供任何插槽内容时,父组件中的<SubmitButton>
的内容会默认渲染 --Submit-- 。
如果父组件提供了插槽的内容:
template
//父组件
<div>
<SubmitButton>--Save--</SubmitButton>
</div>
它会取代默认内容,渲染结果为 --Save--
具名插槽
当一个组件中需要多个插槽出口 时,<slot>
元素可以有一个特殊的 attribute name
,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:
template
<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>
元素,并将目标插槽的名字传给该指令,
template
<BaseLayout>
<template v-slot:header>
<!-- header 插槽的内容放这里 -->
</template>
</BaseLayout>
v-slot
有对应的简写 #
, <template v-slot:header>
可以简写为 <template #header>
。其意思就是"将这部分模板片段传入子组件的 header 插槽中"。

动态插槽名
动态指令参数在 v-slot
上也是有效的,即可以定义下面这样的动态插槽名
template
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
例:
template
//子组件
<template>
<div class="btn">
<slot v-for="item in list" :name="item"></slot>
</div>
</template>
<script setup>
import { ref } from "vue";
const list = ref(['one', 'two', 'three'])
</script>
template
//父组件
<template>
<myButtonVue>
<template v-for="item in list" #[item]>
{{ item }}
</template>
</myButtonVue>
</template>
<script setup lang="ts">
import myButtonVue from "./components/myButton.vue";
import { ref} from "vue"
const list = ref(['one', 'two', 'three'])
</script>
作用域插槽
当在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
template
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
通过子组件标签上的 v-slot
指令,直接接收一个插槽 props 对象:
template
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
也可以在 v-slot
中使用解构:
template
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
具名作用域插槽
具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot
指令的值被访问到:v-slot:name="slotProps"
。
template
<slot name="header" message="hello"></slot>
template
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
</MyComponent>
注意插槽上的 name
是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终 headerProps
的结果是 { message: 'hello' }
。