Vue插槽

简介

  • 在Vue.js中,插槽(Slot)是一种机制,允许你在父组件中定义一些模板内容,然后将这些内容传递到子组件中进行渲染。这样,你就可以在父组件和子组件之间进行更加灵活的组件组合。
  • vue组件能够接收任意类型的 JavaScript 值作为 props,但组件要如何接收模板内容呢?在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。
  • 在 Vue 3 中,插槽的使用方式有所改变,引入了新的 <slot> 元素以及 <template> 中的特殊语法。下面是一些关于 Vue 3 插槽的基本用法:

基本插槽

在父组件中,你可以使用 <slot> 元素指定插槽的位置:

1
2
3
4
5
6
<!-- ParentComponent.vue -->
<template>
<div>
<slot></slot>
</div>
</template>

在子组件中,你可以将内容插入到插槽中:

1
2
3
4
5
6
<!-- ChildComponent.vue -->
<template>
<ParentComponent>
<p>This content will be inserted into the slot.</p>
</ParentComponent>
</template>

默认插槽

在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如有这样一个子组件。

1
2
3
4
5
<template>
<div>
<slot>默认插槽文本</slot> <!-- 默认插槽 -->
</div>
</template>

在父组件中如果不输入任何插槽内容则展示子组件默认插槽的原始内容。如果输入了插槽内容则展示展示父组件插槽的内容。

1
2
3
4
5
6
<template>
<!-- 读取默认插槽内容 -->
<Child/>
<!-- 替换默认插槽 -->
<Child>替换默认插槽new</Child>
</template>

具名插槽

有时在一个组件中包含多个插槽出口是很有用的,例如我们在子组件中定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="container">
<header>
<!-- 具名插槽 -->
<slot name="header">header默认内容</slot>
</header>
<main>
<!-- 默认插槽 -->
<slot>main默认内容</slot>
</main>
<footer>
<!-- 具名插槽 -->
<slot name="footer">footer默认内容</slot>
</footer>
</div>
</template>

<slot> 元素可以有一个特殊的属性 name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容。这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name<slot> 会隐式地命名为“default”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<Child>
<template v-slot:header>
父组件header部分
</template>
<template #default>
父组件默认内容
</template>
<template #footer>
父组件header部分
</template>
</Child>
</template>

<script setup>
import Child from '~/components/Child.vue'
</script>

在父组件中使用时,我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到具名插槽了:
要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 <template>元素,并将目标插槽的名字传给该指令:v-slot:插槽名称
v-slot 有对应的简写 #,因此 <template v-slot:header> 可以简写为 <template #header>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。所以前面的我们可以改下如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<Child>
<template v-slot:header>
父组件header部分
</template>

<!-- 隐式默认插槽 -->
<p>父组件默认内容</p>

<template #footer>
父组件header部分
</template>
</Child>
</template>

动态插槽

我们可以在父组件中通过指令v-solt参数来动态调用具名插槽,例如子组件定义如下:

1
2
3
4
5
6
7
8
<template>
<div>
<!-- 具名插槽 -->
<slot name="header"></slot>
<slot name="title"></slot>
<slot></slot> <!-- 默认插槽 -->
</div>
</template>

父组件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<Child :slotName="dynamicSlotName">
<!-- 具名插槽内容 -->
<template v-slot:[dynamicSlotName]>
<!--v-slot可以简写# <template #:[dynamicSlotName]>-->
<p>This is dynamic content for slot: {{ dynamicSlotName }}</p>
</template>
<!-- 默认插槽内容 -->
<p>This is default content.</p>
</Child>
</div>
</template>

<script setup>
import Child from '~/components/Child.vue'
import { ref } from 'vue';


const dynamicSlotName = ref('title');

</script>

父组件可以根据响应式变量dynamicSlotName的值来调用具名插槽,界面渲染如下

1
2
This is dynamic content for slot: title
This is default content.

渲染作用域

插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。举例来说:

1
2
3

<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>

这里的两个 {{ message }} 插值表达式渲染的内容都是一样的。

插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:

父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

作用域插槽

在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。

默认插槽作用域

例如在下面子组件中插槽定义了2个属性textcount

1
2
3
4
5
6
7
8
9
<template>
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
</template>

<script setup>
const greetingMessage='childMessage'
</script>

在父组件中我们可以通过子组件标签上的 v-slot 指令,直接接收到了一个插槽props对象,使用 lotProps.text和 slotProps.count即可读取子组件的属性值。

1
2
3
4
5
6
7
8
9
<template>
<Child v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</Child>
</template>

<script setup>
import Child from '~/components/Child.vue'
</script>

页面输出如下

1
childMessage 1

具名插槽作用域

具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name="slotProps" .

注意:如果你同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 <template> 标签。

例如子组件定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="container">
<header>
<!-- 具名插槽 -->
<slot name="header" message="header" count="2024">header默认内容</slot>
</header>
<main>
<!-- 默认插槽 -->
<slot message="default" count="2023">main默认内容</slot>
</main>
<footer>
<!-- 具名插槽 -->
<slot name="footer">footer默认内容</slot>
</footer>
</div>
</template>

父组件定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<Child>
<!-- 获取具名插槽属性值 -->
<template #header="headerSlot">
{{headerSlot}}
</template>

<!-- 获取默认插槽属性值 -->
<template #default="{ message,count }">
<p> {{message}} {{count}}</p>
</template>

</Child>
</template>

<script setup>
import Child from '~/components/Child.vue'
</script>

渲染结果

1
2
3
{ "message": "header", "count": "2024" }
default 2023
footer默认内容

参考资料