一、为什么使用 Slot

开发中,会经常封装一个个可复用的组件

前面通过 props 传递给组件一些数据,让组件来进行展示

  • 但是为了让这个组件具备更强的通用性,不能将组件中的内容限制为固定的 div、span 等等这些元素
  • 比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下使用组件希望显示的是一张图片
  • 应该让使用者可以决定某一块区域到底存放什么内容和元素

举个栗子:假如定制一个通用的导航组件 - NavBar

  • 这个组件分成三块区域:左边-中间-右边,每块区域的内容是不固定
  • 左边区域可能显示一个菜单图标,也可能显示一个返回按钮,可能什么都不显示; 中间区域可能显示一个搜索框,也可能是一个列表,也可能是一个标题,等等;右边可能是一个文字,也可能是一个图标,也可能什么都不显示

二、使用 Slot

插槽的使用过程其实是抽取共性、预留不同

将共同的元素、内容依然在组件内进行封装

同时将不同的元素使用 slot 作为占位,让外部决定到底显示什么样的元素

  • Vue 中将 slot 元素作为承载分发内容的出口
  • 在封装组件中,使用特殊的元素就可以为封装组件开启一个插槽
  • 该插槽插入什么内容取决于父组件如何使用

Slot.vue 定义插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<h2>组件开始</h2>
<slot>
<h4>默认内容</h4>
</slot>
<h2>组件结束</h2>
</template>

<script>
export default {};
</script>

<style lang="scss" scoped></style>

App.vue 使用插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<slot-cpn>
<button>使用插槽进行展示</button>
</slot-cpn>

<!-- 一个插槽,多个元素 都会进行展示 -->
<slot-cpn>
<h2>2332323</h2>
<span>jkshfhlsdf</span>
<h2>ookk</h2>
</slot-cpn>

<!-- 不添加元素,展示默认内容 -->
<slot-cpn> </slot-cpn>
</div>
</template>

<script>
import SlotCpn from "./SlotCpn.vue";
export default {
components: { SlotCpn },
};
</script>

<style lang="scss" scoped></style>

插入普通的内容、html 元素、组件元素,都可以是可以的

默认的内容只会在没有提供插入的内容时,才会显示

具名插槽

如果定义了多个插槽,且插入了多个元素,会怎么进行展示?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<h2>组件开始</h2>
<slot>
<h4>默认内容</h4>
</slot>

<slot>
<h4>默认内容</h4>
</slot>

<slot>
<h4>默认内容</h4>
</slot>
<h2>组件结束</h2>
</template>

<slot-cpn>
<h2>2332323</h2>
<span>jkshfhlsdf</span>
<h2>ookk</h2>
</slot-cpn>

默认情况下每个插槽都会获取到我们插入的内容来显示

希望达到的效果是插槽对应的显示,这个时候我们就可以使用 具名插槽

具名插槽顾名思义就是给插槽起一个名字, 元素有一个特殊的 attribute:name

一个不带 name 的 slot,会带有隐含的名字 default

动态插槽名

目前使用的插槽名称都是固定的

比如 v-slot:left、v-slot:center 等等

可以通过 v-slot:[dynamicSlotName]方式动态绑定一个名称

1
2
3
<template v-slot:[name]></template>

data() { return { name: "why" } }

具名插槽缩写

跟 v-on 和 v-bind 一样,v-slot 也有缩写

即把参数之前的所有内容 (v-slot:) 替换为字符 #

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div>
<nav-bar :name="name">
<template #left>
<button>左边的按钮</button>
</template>
<template #center>
<h2>我是标题</h2>
</template>
<template #right>
<i>右边的i元素</i>
</template>
<template #[name]>
<i>why内容</i>
</template>
</nav-bar>
</div>

渲染作用域

在 Vue 中有渲染作用域的概念:

  • 父级模板里的所有内容都是在父级作用域中编译的
  • 子模板里的所有内容都是在子作用域中编译的

作用域插槽

有时候我们希望插槽可以访问到子组件中的内容是非常重要的:

当一个组件被用来渲染一个数组元素时,我们使用插槽,并且希望插槽中没有显示每项的内容

这个 Vue 给我们提供了作用域插槽

来看下面的一个案例:

1.在 App.vue 中定义好数据

2.传递给 ShowNames 组件中

3.ShowNames 组件中遍历 names 数据

4.定义插槽的 prop

5.通过 v-slot:default 的方式获取到 slot 的 props

6.使用 slotProps 中的 item 和 index

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<show-names :names="names">
<template v-slot="slotProps">
{{ slotProps.item }} --- {{ slotProps.index }}
</template>
</show-names>
</div>
</template>

<script>
import ShowNames from "./ShowNames.vue";

export default {
components: { ShowNames },
data() {
return {
names: ["xx", "tt", "uu"],
};
},
};
</script>

ShowNames.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<template v-for="(item, index) in names">
<slot :item="item" :index="index"></slot>
</template>
</div>
</template>

<script>
export default {
props: {
names: {
type: Array,
default: () => {},
},
},
};
</script>

注意:slotProps 不是固定的,可以自己更改

独占默认插槽

如果插槽是默认插槽 default,那么在使用的时候 v-slot:default=”slotProps”可以简写为 v-slot=”slotProps”:

并且如果插槽只有默认插槽时,组件的标签可以被当做插槽的模板来使用,这样,我们就可以将 v-slot 直接用在组件上

1
2
3
<slot-names :names="names" v-slot="slotProps">
<span>{{slotProps.item}}</span>
</show-names>

但是,如果有默认插槽和具名插槽,那么按照完整的 template 来编写

只要出现多个插槽,请始终为所有的插槽使用完整的基于