Appearance
Vue3教程 - 19 插槽
和 Vue2 中的用法基本一样。
Vue 中的插槽(Slot)是一种组件间通信的技术,它允许父组件向子组件中插入 HTML 结构或组件。
使用场景,在不同的父组件中使用某个子组件的时候,想要动态的渲染子组件的内容。子组件的部分内容可以从父组件中“传递”到子组件进行显示。
19.1 插槽是什么
下面看一个例子
写一个子组件:ChildCom.vue
vue
<template>
<div>
<div>我是子组件</div>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
</script>写一个父组件:HomePage.vue。
在父组件中使用子组件 ChildCom。
vue
<template>
<div>
<div>我是父组件</div>
<ChildCom>
<p>测试一下,把内容写在这里,看是否能否显示</p>
</ChildCom>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import ChildCom from '@/components/ChildCom.vue';
</script>运行代码,效果如下:
可以看到在父组件中,使用子组件的时候,在子组件标签中间添加的内容是看不见的。
那如果我想实现显示父标签中,子标签中间添加的内容,该怎么办?
修改子组件:ChildCom.vue ,添加一个 <slot></slot> 标签:
vue
<template>
<div>
<div>我是子组件</div>
<slot></slot>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
</script>在子组件中添加了一个 slot 插槽。那么在父组件中使用子组件的时候,子标签中的内容就回显示到子组件的 slot 插槽中。
运行代码,显示效果:

19.2 插槽的分类
Vue 中的插槽主要分为以下几种类型:
- 默认插槽(匿名插槽)
- 具名插槽
- 作用域插槽
1 默认插槽
在上面演示的时候,使用的就是默认插槽。
下面再举个例子。
例如在一个弹出框的子组件中,想要控制弹出框中按钮的文本,我们可能希望这个 <button> 内绝大多数情况下都渲染文本 确定,但是有时候却希望渲染文本为提交,那怎么实现呢?
我们可以将 确定 作为默认内容,我们可以将它放在 <slot> 标签内:
定义子组件:DialogCom.vue
vue
<template>
<div>
<button type="submit">
<slot>确定</slot>
</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
</script>现在当我在一个父级组件中使用子组件 <DialogCom> ,并且不提供任何插槽内容时:
vue
<template>
<div>
<div>我是父组件</div>
<DialogCom></DialogCom>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import DialogCom from '@/components/DialogCom.vue';
</script>显示如下:

但是如果我们提供内容:
vue
<DialogCom>
提交
</DialogCom>则默认内容将会被父组件提供的内容覆盖:

2 具名插槽
有时候我们希望通过父组件向子组件传递多个结构和组件,将不同的内容渲染到不同的区域,这个时候就需要用到具名插槽了。
可以通过给 <slot> 标签添加 name 属性来定义具名插槽。父组件在填充内容时,需要使用 v-slot 指令(或简写为 #)加上插槽名称来指定内容应该填充到哪个插槽中。
例如定义子组件:ChildCom.vue:
vue
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
</script>在子组件中定义了多个插槽,那么可以在父组件中根据插槽的名称传递。
父组件:HomePage.vue
vue
<template>
<div>
<div>我是父组件</div>
<ChildCom>
<template v-slot:header>
<h1>这是头部内容</h1>
</template>
<p>这是默认插槽的内容</p>
<template v-slot:footer>
<p>这是底部内容</p>
</template>
</ChildCom>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import ChildCom from '@/components/ChildCom.vue';
</script>在使用具名插槽的时候,推荐使用 <template> 元素将内容包裹,然后使用 v-slot 指令指定插槽的名称。 <template> 标签本身不会被渲染成 DOM 元素,它仅仅是一个包裹元素,用于包含和指定插槽内容。如果使用 div 等元素,会额外多一层DOM。
运行结果:

子组件中,一个不带 name 的 <slot> 插槽,默认的名字是 default。 所以父组件中也可以通过如下方式向默认插槽传递:
vue
<template #default>
<p>这是默认插槽的内容</p>
</template>具名插槽在书写的时候可以使用缩写,v-slot 可以用 # 来代替:
vue
<template>
<div>
<div>我是父组件</div>
<ChildCom>
<template #header>
<h1>这是头部内容</h1>
</template>
<p>这是默认插槽的内容</p>
<template #footer>
<p>这是底部内容</p>
</template>
</ChildCom>
</div>
</template>3 作用域插槽
这里主要解决的是父组件在向子组件插槽传递模板内容时,允许传递的模板访问子组件的数据。这样,父组件就可以根据这些数据来动态渲染内容。
举个栗子:
子组件定义作用域插槽
在子组件的模板中,通过 <slot> 标签的 v-bind(或简写为 :)绑定数据,这些数据就可以在父组件的插槽内容中使用了。
定义子组件:ChildCom.vue
vue
<template>
<div>
<span>
<!-- 父组件就可以操作user了 -->
<slot :user="user"></slot>
</span>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue';
let user = ref({
username: "doubi",
age: 13
});
</script>在上面的代码中,通过 v-bind(简写为 :)绑定数据到插槽,这样父组件就可以操作 user 数据了。
父组件使用作用域插槽
在父组件中,通过 <template> 元素和 v-slot 指令(或缩写为#)来接收子组件传递的数据,并定义如何渲染这些数据。
vue
<template>
<div>
<div>我是父组件</div>
<ChildCom>
<!-- 父组件可以操作插槽的数据 -->
<template v-slot:default="slotProps">
<p>{{ slotProps.user.username }}</p>
<p>{{ slotProps.user.age }}</p>
</template>
</ChildCom>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import ChildCom from '@/components/ChildCom.vue';
</script>- 上面例子,我们将包含插槽数据对象命名为
slotProps,名称是自定义的,然后可以操作数据了。 - 因为子组件中插槽是没有名称的,所以这里使用
v-slot:default是给默认插槽传递模板。
也可以使用简写方式给默认插槽传递:
vue
<!-- 方式1 -->
<template v-slot="slotProps">
<p>{{ slotProps.user.username }}</p>
<p>{{ slotProps.user.age }}</p>
</template>
<!-- 方式2 -->
<template #default="slotProps">
<p>{{ slotProps.user.username }}</p>
<p>{{ slotProps.user.age }}</p>
</template>通过作用域插槽,就可以在父组件中决定子组件的数据如何渲染,例如下面两次使用了 ChildCom 组件,使用的渲染方式不同:
vue
<template>
<div>
<div>我是父组件</div>
<ChildCom>
<!-- 父组件可以操作插槽的数据 -->
<template v-slot:default="slotProps">
<p>{{ slotProps.user.username }}</p>
<p>{{ slotProps.user.age }}</p>
</template>
</ChildCom>
<ChildCom>
<template v-slot:default="slotProps">
<ul>
<li>{{ slotProps.user.username }}</li>
<li>{{ slotProps.user.age }}</li>
</ul>
</template>
</ChildCom>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import ChildCom from '@/components/ChildCom.vue';
</script>
<style scoped>
p {
background-color: yellowgreen;
}
ul {
background-color: lightblue;
}
</style>显示效果:

下面再看一下,如果是多个插槽的情况,子组件:
ChildCom.vue :
vue
<template>
<div>
<span>
<!-- 父组件就可以操作user了 -->
<slot name="header" :user="user"></slot>
<!-- 父组件就可以操作hobby了 -->
<slot name="footer" :hobby="hobby"></slot>
</span>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue';
let user = ref({
username: "doubi",
age: 13
});
let hobby = ref({
fruit: "apple",
color: "blue"
});
</script>父组件:
vue
<template>
<div>
<div>我是父组件</div>
<ChildCom>
<!-- 父组件可以操作插槽的数据 -->
<template #header="slotProps">
<p>{{ slotProps.user.username }}</p>
<p>{{ slotProps.user.age }}</p>
</template>
<template #footer="slotProps">
<p>{{ slotProps.hobby.fruit }}</p>
<p>{{ slotProps.hobby.color }}</p>
</template>
</ChildCom>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import ChildCom from '@/components/ChildCom.vue';
</script>内容未完......