在 Vue3 中,插槽(slot)是一种重要的组件复用和内容分发机制。通过使用插槽,可以让组件更加灵活和具有可复用性,在不同的地方渲染不同的内容,同时保证相同的样式。
官网介绍:https://cn.vuejs.org/guide/components/slots
由于插槽内容本身是在父组件中定义的,所以可以访问到父组件的数据作用域,但是无法访问到子组件的数据作用域。这是因为Vue表达式的词法作用域规则和JavaScript一样,只能访问其定义时所处的作用域。用官方文档的话来说,父组件模版中的表达式只能访问父组件的作用域;子组件模版中的表达式只能访问子组件的作用域。
插槽的基本使用很简单,他类似于一个插排,插槽就是一个一个的口,可以随意的插东西进去。
比如说现在我写了一个 slotModel.vue
组件:
<template>
<div>
<p style="color: green;">我是插槽组件</p>
<slot></slot>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped></style>
其中 <slot></slot>
标签就是插槽。
父组件使用这个子组件:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<slotModel></slotModel>
</template>
<script setup lang="ts">
import slotModel from './slotModel.vue';
</script>
<style scoped>
.ed-p {
margin: 20px;
color: hotpink;
font-size: 20px;
font-weight: 550;
}
</style>
我们看一下效果:
看,像普通组件一样可以加载显示出来。
但是我们改一下,在引用组件的时候,我们传递一个数据进去:
<slotModel>你好,我是ed.</slotModel>
然后我们发现,数据显示出来了:
这里我们传进去的数据,会在插槽组件的 <slot></slot>
标签处作为内容进行显示,这!就是插槽!!
OK,插槽除了可以展示基本的文本信息外,还可以插入标签。
比如我们插入一个按钮进去:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<slotModel>
<button>按钮</button>
</slotModel>
</template>
然后页面就会变成插入的按钮:
好的,这是最基本的使用。
再说一嘴,我们在父组件插入的是:
<slotModel>
<button>按钮</button>
</slotModel>
他会把插槽组件的 <slot></slot>
部分替换成 <button>按钮</button>
,如果插槽组件有多个 <slot></slot>
的话,会同时替换,比如:
<template>
<div>
<p style="color: green;">我是插槽组件</p>
<slot></slot>
<slot></slot>
<slot></slot>
</div>
</template>
现在插槽组件有三个口,那么这三个口都被被 <button>按钮</button>
给替换掉:
看,都替换了吧!好的,就这样。
看上面的案例,我们在插槽的部分渲染了按钮,因为我们父组件传进去的就是按钮,所以可以正常渲染,那么问题来了,如果我不传,我会显示啥!我们试一下,首先我们修改一下插槽组件,只留一个口:
<template>
<div>
<p style="color: green;">我是插槽组件</p>
<slot></slot>
</div>
</template>
然后我们父组件使用插槽的时候,我们不传东西进去:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<slotModel>
<!-- 不传东西 -->
</slotModel>
</template>
我们不传的话:
插口部分直接就是空了,没有东西渲染出来。那么我想在没有数据传进来得时候,插口默认一个数据,比如默认显示“暂无数据”四个字怎么办?
so easy!只需要在插口上写上就可以了:
<template>
<div>
<p style="color: green;">我是插槽组件</p>
<slot>暂无数据</slot>
</div>
</template>
这个 <slot></slot>
是用来替换父组件传进来得内容的,简单理解成占位符哈,不恰到,先这样理解。如果没有传肯定是空,但是我们在插口上添加了自己的内容,就像案例上面的 <slot>暂无数据</slot>
一样,这个时候:如果父组件传递了内容进来,则还是和之前一样替换掉<slot></slot>
,如果没有传递,则默认展示<slot></slot>
中的内容,所以这样的话,在父组件不传递内容进来的时候,就会显示 “暂无数据” 四个字了:
看默认数据显示出来了, 当然我案例用的文本,其实标签啥的都是可以的哈,我就不写了,自己试一下就可以了!
具名插槽的意思就是带有名字的插槽,什么时候用呢,打个比方哈,就譬如说:我一个组件里面有插槽,但是我可能有好几个插口,我想分别用来显示不同的内容,比如下面的内容哈:
<template>
<div style="background-color: green;padding:20px;color:#fff">
<!-- 插口一 -->
<slot></slot>
</div>
<div style="background-color: red;padding:20px;color:#fff">
<!-- 插口二 -->
<slot></slot>
</div>
<div style="background-color: yellow;padding:20px;color:#fff">
<!-- 插口三 -->
<slot></slot>
</div>
</template>
上面代码,我这一个组件有三个插口,如果我们父组件传进来一个内容看一下效果:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel>
<button>按钮</button>
</slotModel>
</template>
我们在父组件,往插口组件传进一个按钮,会有什么效果?上面说了一遍了其实,同时替换:
看,三个插口都被替换了。
假设我现在想让 插口一显示“插口一”按钮,插口二显示“插口二”按钮,插口三显示“插口三”按钮
,怎么办?这个时候就需要用到具名插槽
了,我们给每一个插口设置一个名字,然后父组件传内容的时候,根据名字来确定放在哪个插口里面。
比如我们先给插口起一个名字:
<template>
<div style="background-color: green;padding:20px;color:#fff">
<!-- 插口一 -->
<slot name="one"></slot>
</div>
<div style="background-color: red;padding:20px;color:#fff">
<!-- 插口二 -->
<slot name="two"></slot>
</div>
<div style="background-color: yellow;padding:20px;color:#fff">
<!-- 插口三 -->
<slot name="three"></slot>
</div>
</template>
我们给每个插槽起了一个名字,然后我们父组件就可以根据名字设置具体是给哪个插槽传递的内容 :
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel>
<template v-slot:one>
<button>插口一</button>
</template>
<template v-slot:two>
<button>插口二</button>
</template>
<template v-slot:three>
<button>插口三</button>
</template>
</slotModel>
</template>
template 标签
啥作用我就不说了哈,学 vue 的都知道,他只是作为容器包裹一下,不会实际渲染出来。只需要知道,要为具名插槽
传入内容,我们需要使用一个含 v-slot
指令的 <template>
元素,并将目标插槽的名字
传给该指令,我们主要看 v-slot:one
,我们通过 v-slot: + 插槽名
的方式来标明该部分的内容通过哪个插槽来渲染,即渲染到哪个插槽。看渲染结果:
看,完全没有问题!
其实这个 v-slot: + 插槽名
还有一个简写的方式,就是 # + 插槽名
,效果是一样的,比如说插口一:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel>
<template #one>
<button>插口一</button>
</template>
<template v-slot:two>
<button>插口二</button>
</template>
<template v-slot:three>
<button>插口三</button>
</template>
</slotModel>
</template>
我们可以看一下,效果是完全一样的:
总结一下:v-slot
有对应的简写 #
,因此 <template v-slot:one>
可以简写为 <template #one>
。其意思就是“将这部分模板片段传入子组件的 one 插槽中”
。
有一点需要特别说一下,你看我们在插槽组件里面都给插槽设置了名字,那我如果在父组件使用的时候,不使用 v-slot: + 插槽名
设置渲染到哪个插槽里面会出现什么情况?
看下面父组件的代码:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel>
<button>插口一</button>
<template v-slot:two>
<button>插口二</button>
</template>
<template v-slot:three>
<button>插口三</button>
</template>
</slotModel>
</template>
看上面代码,我在插口一的那部分,没有说设置给哪个插槽渲染,这时候会出什么问题?
看结果,直接不显示了,因为没有插槽能渲染他。
那我就想渲染一个没有设置插槽名字的内容怎么办?其实很简单,写一个不就完事了:
<template>
<div style="background-color: green;padding:20px;color:#fff">
<!-- 插口一 -->
<slot name="one"></slot>
</div>
<div style="background-color: red;padding:20px;color:#fff">
<!-- 插口二 -->
<slot name="two"></slot>
</div>
<div style="background-color: yellow;padding:20px;color:#fff">
<!-- 插口三 -->
<slot name="three"></slot>
</div>
<div style="background-color: blue;padding:20px;color:#fff">
<!-- 插口四 -->
<slot></slot>
</div>
</template>
看代码,我们增加了一个插口四,没有设置插口的名字,这时候,父组件没设置名字的内容就会在这里显示:
所以说:这类带 name
的插槽被称为具名插槽 (named slots)
。没有提供 name 的 <slot>
出口会隐式地命名为“default
”。
所以 <slot></slot>
和 <slot name="default"></slot>
是一样的。
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。
比如说下面的例子。
首先我们插槽组件只留一个插槽:
<template>
<div style="background-color: green;padding:20px;color:#fff">
<slot></slot>
</div>
</template>
然后我们在父组件定义一个变量:
const msg = ref("你好,我是ed.")
然后我们在父组件插槽内容里面使用一下这个变量:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel>
<button>{{ msg }}</button>
</slotModel>
</template>
能不能正常显示?理论上这个<button>{{ msg }}</button>
是要传进子组件,然后子组件把 <slot></slot>
替换成父组件传进来的 <button>{{ msg }}</button>
,那么这个 msg
变量可以正常显示不?我们看一下效果:
OK。可以显示!所以:插槽内容可以访问到父组件的数据作用域,插槽内容无法访问子组件的数据。也就是说 Vue 模板中的表达式只能访问其定义时所处的作用域。
再来!
给这个按钮添加一个点击事件,然后在父组件和子组件都写他的回调,请问他会走谁的?
父组件:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel>
<button @click="btnClick">{{ msg }}</button>
</slotModel>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import slotModel from './slotModel.vue';
const msg = ref("你好,我是ed.")
const btnClick = () => {
console.log("父组件 --- btnClick");
}
</script>
<style scoped>
.ed-p {
margin: 20px;
color: hotpink;
font-size: 20px;
font-weight: 550;
}
</style>
子组件:
<template>
<div style="background-color: green;padding:20px;color:#fff">
<slot></slot>
</div>
</template>
<script setup lang="ts">
const btnClick = () => {
console.log("子组件 --- btnClick");
}
</script>
<style scoped></style>
然后我们看一下效果:
我们看到,点击之后打印的是父组件的。
通过上面一部分我们已经知道了:插槽内容可以访问到父组件的数据作用域,插槽内容无法访问子组件的数据。
那么我就是矫情!我非得要插槽内容无法使用子组件的数据怎么办呢?
就是这一节需要用到的内容了,啊哈哈哈哈,作用域插槽!
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
看下面代码,我们在插槽组件定义两个变量:
const name = ref('我是ed.');
const age = ref(25);
然后咱们在 <slot></slot>
插槽里面,可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
<slot :myName="name" :age="age"></slot>
注意,我用的是 myName, 为啥不用 name 呢?因为是定义插槽名称的关键字,避免冲突换了一个哈!!好了,插槽把数据抛出去了,父组件可以直接用了:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel v-slot="slotProps">
<button @click="btnClick">{{ slotProps.myName }} {{ slotProps.age }}</button>
</slotModel>
</template>
好的,看在引入的插槽组件上首先使用 v-slot="slotProps"
接收插槽组件的数量,其实 slotProps
他是一个对象,内容是 {myName: "我是ed.", age:25}
,然后我们在插槽内容就可以使用这个数据了:
完美~
当然如果愿意的话,结构也可以:
<template>
<p class="ed-p">我是ed. vue3 插槽</p>
<!-- 调用插槽组件 -->
<slotModel v-slot="{myName, age}">
<button @click="btnClick">{{ myName }} {{ age }}</button>
</slotModel>
</template>
效果是一样的:
但是! 注意一点,这是默认插槽
的使用方式!具名插槽 的使用方式有一些小区别。
简单写哈,写够了:
<template #two="twoProps">
<button>{{twoProps.myName}}</button>
</template>
ok,就这样写。
好了好了,终于结束了,今天就到这里吧,然后其他内容可以去我博文最开始提供的官网地址去看,好了,结束了,拜拜!!