一文搞懂Vue3中slot插槽的使用!
使用 Vue
的小伙伴相信你一定使用过插槽,如果你没有用过,那说明你的项目可能不是特别复杂。插槽(slot)可以说在一个 Vue
项目里面处处都有它的身影,比如我们使用一些 UI
组件库的时候,我们通常可以使用插槽来自定义我们的内容。
1.插槽基本使用
插槽的用途就和它的名字一样:有一个缺槽,我们可以插入一些东西。
插槽 slot
通常用于两个父子组件之间,最常见的应用就是我们使用一些 UI
组件库中的弹窗组件时,弹窗组件的内容是可以让我们自定义的,这就是使用了插槽的原理。
我们在项目中新建一个 child.vue
文件,用来当作子组件,它的代码也非常的简单。
child.vue 代码如下:
<template>
<div class="child-box">
<p>我是子组件</p>
<!-- 插槽 -->
<slot></slot>
</div>
</template>
<style>
.child-box {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
然后我们直接在 App.vue
引用该子组件,代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child></child>
</template>
<script setup lang="ts">
import Child from "./child.vue";
</script>
输出结果:
child
子组件代码非常的简单,唯一不同的里面我们添加了一对<slot></slot>
标签,这就是插槽标签。这对标签就好比我们在 child
组件内部挖了一个槽出来,我们接下来就可以往这个槽里面放置一些内容。
那么我们如何往这个槽里面放置内容呢?
我们在 App.vue
里面通过一对闭合标签<child></child>
调用了子组件,我们都知道 HTML
标签之间是可以插入内容的,虽然 child
不是 HTML
自带的标签,但是它却有着类似的特征,比如我们往<child></child>
之间插入一点内容,代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<div>小猪课堂</div>
</child>
</template>
输出结果:
我们发现在<child></child>
标签之间插入的内容被渲染出来了,那么这是为何呢?
前面我们说 slot
就是挖了一个槽出来,可以放置东西,这里我们在父组件中添加的 div
便就是我们要添加的东西,子组件中 slot
标签被替换为了我们插入的 div
元素。这就是插槽的最基本使用。
结论:
为了更好理解插槽,简单总结为以下几点:
-
slot
是Vue3
中的内置标签。 -
slot
相当于给子组件挖出了一个槽,可以用来填充内容。 - 父组件中调用子组件时,子组件标签之间的内容元素就是要放置的内容,它会把
slot
标签替换掉。
最简单的理解: 我们的使用 U
盘需要将 U
盘插入 USB
口中,此时 USB
口就是插槽,U
盘就是插口。在 Vue
中,<slot></slot>
就是电脑插槽,父组件的内容就可以理解为 U
盘插口。
3.插槽默认内容
我们通常将插槽比作一个占位符,有内容进来时,自动把 slot
给替换掉。但是,如果没有内容进来时,那么应该渲染什么呢?
在很多场景下都会有这种需求,比如 UI
组件库中的弹窗,如果我们没有传入弹窗的头部或者底部,那么弹窗便会有默认的样式效果,这里就用到了 slot
的默认内容功能。
修改一下 child.vue 代码:
<template>
<div class="child-box">
<p>我是子组件</p>
<!-- 插槽 -->
<slot>
<p>我是默认内容</p>
</slot>
</div>
</template>
当我们的 App.vue
没有向 child
组件传入内容时,会是什么效果呢?
App.vue 代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child></child>
</template>
输出结果:
可以看到子组件同样渲染了内容,而且就是 slot
标签内的内容。那么我们我们往 child
组件传入一点内容会是什么效果呢?
App.vue 代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<div>{{ message }}</div>
</child>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./child.vue";
const message = ref("小猪课堂");
</script>
输出结果:
可以看到此时页面上渲染的 App.vue
父组件传入的内容了。
总结:
从上面例子不难看出,slot 标签内的内容就是默认内容,也就是当父组件没有传递给子组件内容时,子组件就会默认渲染 slot 内部的内容,但是 slot 标签是不会渲染出来的。
4.具名插槽
很多时候我们子组件中都不止只有一个 slot
,比如弹窗组件,我们可能允许调用者同时传入 header
、content
、footer
等等,这个时候如果子组件中只有一个 slot
,那么这么多内容该如何区分,或者说该如何渲染呢?
4.1 基本使用
这个时候为了区分插槽与内容的对应关系,我们可以分别给 slot
和内容都加上一个名字,插入插槽的时候大家按照名字区分好就可以了。
我们给 child
组件添加上多个 slot
,并且给每个 slot
取上一个名字。
代码如下:
<template>
<div class="child-box">
<p>我是子组件</p>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
上段代码中我们添加了 3
个 slot
插槽,并且给其中两个 slot
标签添加了一个 name
属性,也就是每个插槽的名字。需要注意的是,上段代码中有一个插槽我们没有添加 name
属性,这个时候 Vue
会隐式的将这个插槽命名为“default
”,接下来就是我们父组件 App.vue
添加内容了。
代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template v-slot:header>
<div>我是 header:{{ message }}</div>
</template>
<div>我没有名字:{{ message }}</div>
<template v-slot:footer>
<div>我是 footer:{{ message }}</div>
</template>
</child>
</template>
输出结果:
既然 slot
有了名字,那么我们在父组件中传入内容时就要和名字关系对应起来,我们采用 v-slot:header
指令的形式找到对应的插槽,需要注意的是该指令需要作用在 template
元素上。从上图可以看出,我们传入的内容都渲染到了对应的插槽内,没有命名的插槽渲染了我们传入的未添加指令的内容。
4.2 简写
在 Vue
中,很多指令都有简写形式,v-slot:name
指令也有简写形式,比如看我们下面的示例。
原写法:
<template v-slot:footer>
</template>
简写法:
<template #footer>
</template>
接下来我们在借用官网的一张图来清楚的了解具名插槽中的父子组件关系。
其中 BaseLayout
为一个子组件,就和我们 child
组件一样。
4.3 默认插槽与具名插槽混用
当一个子组件中既有具名插槽,又有默认插槽时,该如何渲染呢?
前面我们说默认插槽会被隐式的命名为 default
,所以我们传入内容时可以将插槽名字改为 defalut
即可。
修改 child 组件:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template v-slot:header>
<div>我是 header:{{ message }}</div>
</template>
<template v-slot:default>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
</template>
<template v-slot:footer>
<div>我是 footer:{{ message }}</div>
</template>
</child>
</template>
虽然说子组件中有一个 slot
没有取名字,但是默认可以用 default
来代表它,就好比人刚出生的时候没有名字,但是他又一个默认属性:“人”。
当然,既然大家都是默认的,在父组件中你也可以不用名字,这个时候内容会默认传入到未命名插槽中去。
代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template v-slot:header>
<div>我是 header:{{ message }}</div>
</template>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
<template v-slot:footer>
<div>我是 footer:{{ message }}</div>
</template>
</child>
</template>
输出结果:
5.动态插槽名
前面我们给插槽命名的时候都是直接写死的,其实我们有时候可以动态给插槽命名的,以满足更多的业务场景。
代码如下:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
上段代码就是在父组件中采用动态插槽名传入内容的示例。
6.插槽作用域问题
我们仔细思考插槽的使用,会发现这有一点类似于父子组件传递,只不过插槽传递的是模板内容罢了。那么涉及到传值,就会有一个作用域的问题,我们回顾一下 App.vue
中的一段代码。
代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<div>{{ message }}</div>
</child>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./child.vue";
const message = ref("小猪课堂");
</script>
上段代码中 message
是我们在父组件中定义的数据,但是在我们的子组件 child
中渲染了出来,说明子组件中的插槽是可以访问到父组件中的数据作用域的,但是反过来是不行的,因为我们无法通过插槽拿到子组件的数据。
总结:
所以我们这里总结为两点
- 插槽内容可以访问到父组件的数据作用域,就好比上述中的
message
是父组件的。 - 插槽内容无法访问到子组件的数据,就好比上述
App.vue
中的插槽内容拿不到子组件child
的数据。
7.作用域插槽
前一节我们说父组件中的插槽内容是无法访问到子组件中的数据的,但是,万一我们有需求就是需要在插槽内容中获取子组件数据怎么办呢?
Vue3
给我们提供了方法,使用起来也比较简单。
7.1 默认插槽作用域传值
我们先来演示默认插槽如何获取子组件的数据。
child.vue 代码如下:
<template>
<div class="child-box">
<p>我是子组件</p>
<slot text="我是子组件小猪课堂" :count="1"></slot>
</div>
</template>
App.vue 组件代码:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child v-slot="slotProps">
<div>{{ slotProps.text }}---{{ slotProps.count }}</div>
</child>
</template>
输出结果:
上段代码中我们在子组件中 slot
标签上添加了一些自定义属性,属性值就是我们想要传递给父组件的一些内容。在父组件 App.vue
中通过 v-slot="slotProps"
等形式接收子组件传毒过来的数据,slotProps
的名字是可以任意取的,它是一个对象,包含了所有传递过来的数据。
需要注意的是,子组件传递过来的数据只能在子组件这个标签内使用。
解构写法:
我们都知道对象是可以解构的,所以我们在父组件中还可以直接使用解构的写法来获取数据。
代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child v-slot="{ text, count }">
<div>{{ text }}---{{ count }}</div>
</child>
</template>
7.2 具名插槽作用域传值
具名插槽作用域之间的传递其实默认插槽作用域传值原理是一样的,只不过写法不一样罢了。
child.vue 代码如下:
<template>
<div class="child-box">
<p>我是子组件</p>
<slot name="header" text="我是子组件小猪课堂" :count="1"></slot>
</div>
</template>
App.vue 代码如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template #header="{ text, count }">
<div>{{ text }}---{{ count }}</div>
</template>
</child>
</template>
上段代码中我们给 slot
添加了一个 name
,在父组件中接收数据的时候不在采用 v-slot=""
形式了,而是直接再插槽内容上采用#header=""
形式,当时这是简写形式,你也可以写为:v-slot:header=""
总结
插槽的作用非常广泛,学好插槽对我们的项目开发有着非常大的帮助,当然,想要非常优雅的使用插槽,还是需要费一些功夫的。这里可以推荐大家去看一看element
组件库中的table
的使用,看看它是如何使用插槽的,如何优雅的将表格组件中的数据共享给父组件的。