Vue3 Slot
1.基本使用
顾名思义,插槽的作用就是:在指定的地方有一个槽,可以给我们插入一些东西。
在Vue中,插槽 slot 通常用于两个父子组件之间,其中最为常见的应用就是 UI 组件库,例如:Ant design、ELement等等,它们的弹窗组件、列表组件等其中的部分内容是可以自定义的,这就是使用了插槽来实现的!
下面,我们通过一个实例来学习插槽的使用。
首先在项目中新建一个子组件childCom.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>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<child></child>
</div>
</template>
<script>
import child from './components/childCom.vue'
export default {
name: 'App',
components: {
child,
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
效果如下:
这里我们通过添加<slot></slot>
标签,在 child
组件内部挖了一个槽出来,我们接下来就可以往这个槽里面放置一些内容。
那么,如何插入内容?
如果我们在父组件中,向<child></child>
之间插入一点内容,代码如下:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<child>
<div>这里是自父组件插入的内容</div>
</child>
</div>
</template>
效果如下:
可以看到,在<child></child>
之间插入的内容占据了原本<slot></slot>
标签的位置!所以,这里我们在父组件中添加的div
便就是我们要添加的东西,子组件中slot
标签被替换为了我们插入的div
元素。这就是插槽的最基本使用。
总结一下:
slot
是Vue3
中的内置标签。slot
相当于给子组件挖出了一个槽,可以用来填充内容。- 父组件中调用子组件时,子组件标签之间的内容元素就是要放置的内容,它会把
slot
标签替换掉。
2.插槽默认内容
插槽可以类比一个占位符,当父组件填入了内容,会自动占据 slot
标签的位置,但是,如果父组件中没有填入任何内容,此时 slot 标签位置应该渲染什么内容呢?
实际上,这种情况在很多场景下都会出现,比如前面说到的 UI 组件库的弹窗组件,当我们没有传入自定义的头部、底部等内容,弹窗仍然会有默认的样式效果!这就是用 slot 默认内容来实现的。
修改子组件代码如下:
<template>
<div class="child-box">
<p>子组件</p>
<!-- 插槽 -->
<slot>
<p>默认内容</p>
</slot>
</div>
</template>
<style>
.child-box {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
然后在父组件中,我们不传入任何内容:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child></child>
</template>
效果如下:
可以看到,渲染的是我们设定好的默认内容!
slot
标签内的内容就是默认内容,也就是当父组件没有传递给子组件内容时,子组件就会默认渲染 slot
内部的内容,但是 slot
标签是不会渲染出来的!
3.具名插槽
但是,在较为复杂的业务场景中,往往子组件内部需要不止一个插槽,例如前面说到的 UI 组件库的弹窗组件,其允许调用者同时插入header
、content
和footer
等等自定义内容。
此时,子组件内部不可能会只有一个插槽,但是如果有多个插槽,应该如何区分呢?或者说应该如何进行渲染呢?
3.1 使用
这个时候为了区分插槽与内容的对应关系,我们可以分别给 slot
和内容都加上一个名字,插入插槽的时候大家按照名字区分好就可以了。
如何为插槽加上名称?
Vue中提供了一个属性:name
,我们可以在其中设置插槽的名称:
<slot name="slot1">...</slot>
所以,接下来,我们给 childCom
组件添加上多个 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 添加内容了。
那么,如何向指定名称的插槽中插入内容?
Vue中同样提供了一个指令:v-slot
。
<template v-slot="header">
...
</template>
<!-- 简写形式 -->
<template #header>
...
</template>
但是,需要注意的是,v-slot
指令仅能用于<template></template>
上!
所以我们接下来就可以使用这种方式在父组件中向插槽中添加内容了:
<template>
<div id="app">
<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>
</div>
</template>
效果如下:
可以看出,我们传入的内容都渲染到了对应的插槽内,没有命名的插槽渲染了我们传入的未添加指令的内容。
3.2 默认插槽与具名插槽混合使用
当一个子组件中既有具名插槽,又有默认插槽时,该如何渲染呢?
前面我们说默认插槽会被隐式的命名为 default
,所以我们传入内容时可以将插槽名字改为 defalut
即可。
修改父组件:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template #header>
<div>我是 header</div>
</template>
<template #default>
<div>我没有名字</div>
<div>我没有名字</div>
<div>我没有名字</div>
</template>
<template #footer>
<div>我是 footer</div>
</template>
</child>
</template>
效果如下:
4.动态插槽名
在上面的内容中,所有的插槽命名都是写死的,但实际上我们可以动态地给插槽命名,以适应更多的业务场景。
代码如下:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
这里就是在父组件中,在传入内容时采用动态的插槽名。
5.插槽的作用域问题
通过上面对于插槽的使用,我们可以发现,插槽实际上与父子组件通信有一些类似,只不过插槽中传递的模板内容。那么既然涉及到传值,就会涉及到一个作用域的问题。看下面这段代码:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<div>{{ message }}</div>
</child>
</template>
<script>
import Child from "./child.vue";
...
const message = "这里是一条message!";
</script>
上段代码中 message
是我们在父组件中定义的数据,但是在我们的子组件 child
中渲染了出来,说明子组件中的插槽是可以访问到父组件中的数据作用域的,但是反过来是不行的,因为我们无法通过插槽拿到子组件的数据。
总结来说,有两点:
- 插槽内容可以访问到父组件的数据作用域,就好比上述中的
message
是父组件的。 - 插槽内容无法访问到子组件的数据,就好比上述
App.vue
中的插槽内容拿不到子组件child
的数据。
6.作用域插槽
上面说过,父组件中的插槽无法获取到子组件中的数据,但是,在实际开发中,有一些业务需求就是要求在父组件的插槽内容中获取子组件的数据,那此时应该如何处理呢?
Vue中同样也为此提供了相应的功能:插槽作用域传值。
6.1 默认插槽作用域传值
先来看默认插槽如何传值
首先在childCom
组件中,在slot
标签上通过 props 的形式附带一个值
<template>
<div class="child-box">
<p>我是子组件</p>
<slot text="我是子组件小猪课堂" :count="1"></slot>
</div>
</template>
在父组件中,通过v-slot
来接收 props:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child v-slot="slotProps">
<div>{{ slotProps.text }}---{{ slotProps.count }}</div>
</child>
</template>
效果如下:
注意,这里的slotProps
的名字是可以任意取的,它是一个对象,包含了所有传递过来的数据。而这些子组件传递过来的数据只能在子组件这个标签内使用!
另外,我们都知道,对象是可以解构的,所以我们在父组件中可以直接用解构的形式来获取数据:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child v-slot="{ text, count }">
<div>{{ text }}---{{ count }}</div>
</child>
</template>
6.2 具名插槽作用域传值
了解了默认插槽的作用域传值之后,具名插槽的作用域传值就比较简单了,只不过是将名称与解构混用就行了。
首先在childCom
组件中:
<template>
<div class="child-box">
<p>我是子组件</p>
<slot name="header" text="我是子组件小猪课堂" :count="1"></slot>
</div>
</template>
在父组件中,通过v-slot
来接收 props:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child #header="{ text, count }">
<div>{{ text }}---{{ count }}</div>
</child>
</template>
当然使用v-slot:header="xxx"
也是可以的!
总结
插槽的作用非常广泛,学好插槽对我们的项目开发有着非常大的帮助,当然,想要非常优雅的使用插槽,还是需要费一些功夫的。