前言:在开发中,插槽这个语法可谓太方便了,它解决组件封装需要某些定制化需求。通常我们会在vue中将各种通用的功能单独拿出来进行封装,以便于后面复用,但是有时候需要复用的组件不是完美契合,这个时候就需要用到插槽,slot可以让用户拓展组件,更好的实现组件的复用。
插槽分为默认插槽,具名插槽,作用域插槽这三种。
插槽的用法的也简单,需要两个条件
- 占位符
- 分发内容
组件内部定义的slot标签,我们可以理解为占位符,父组件中插槽内容,就是要分发的内容。插槽处理本质就是将指定内容放到指定位置。
默认插槽
默认插槽是最简单的,在子组件需要地方定义slot,然后在父组件引入该子组件,在该子组件标签内书写你想替换这插槽的内容。
//Test.vue
<template>
<el-card class="box-card">
<slot></slot>
</el-card>
</template>
//App.vue
<script setup>
import TestVue from "./components/Test.vue";
</script>
<template>
<div style="margin: 50px">
<TestVue>
默认插槽
</TestVue>
</div>
</template>
slot自身是支持默认内容,当未给组件提供任何内容的时候,插槽会显示默认内容。
//Test.vue
<template>
<el-card class="box-card">
<slot>子组件内容</slot>//当父组件未提供内容则显示这个
</el-card>
</template>
//App.vue
<template>
<div style="margin: 50px">
<TestVue>
父组件内容 //当父组件提供内容,会把插槽的默认内容给替换掉
</TestVue>
</div>
</template>
注意:默认插槽一般在一个组件最好只存在一个,因为同时存在多个,代码无法区分,会全部进行替换。例如:
<template>
<el-card class="box-card">
<slot>子组件内容</slot>
</el-card>
<el-card>
<slot>插槽2</slot>
</el-card>
</template>
具名插槽
既然默认插槽无法区分,那么我们给它们进行命名,每个插槽都有属于自己的名字,那么就可以区分了。
具名插槽:如果在封装组件时需要预留多个插槽节点,则需要为每个<slot>插槽指定具体的name 名称。
//Test.vue
<template>
<el-card class="box-card">
<slot name="slot1">插槽1</slot>
</el-card>
<el-card>
<slot name="slot2">插槽2</slot>
</el-card>
<el-card>
<slot>默认插槽</slot>
</el-card>
</template>
//App.vue
<div style="margin: 50px">
<TestVue>
<!-- 在向具名插槽提供内容的时候,我们需要在一个 <template> 元素说明你要操作是哪个插槽 -->
<!-- v-slot:名字 -->
<template v-slot:slot1>
父组件替换插槽1
</template>
<!-- #名字 这是v-slot的简写 -->
<template #slot2>
父组件替换插槽2
</template>
<!-- 默认插槽也是有名字的,默认名字为default -->
<template #default>
父组件替换默认插槽
</template>
</TestVue>
</div>
作用域插槽
作用域插槽类似于父子组件之间利用props来传值。在插槽中我们把携带了props数据的插槽就叫作用域插槽。
//Test.vue
<script setup>
const data={
obj:{name:'zhangsan',age:10},
num:100,
date:new Date()
}
</script>
<template>
<el-card class="box-card">
<slot name="slot1" :obj="data.obj">插槽1</slot>
</el-card>
<el-card>
<slot name="slot2" :num="data.num">插槽2</slot>
</el-card>
<el-card>
<slot :date="data.date">默认插槽</slot>
</el-card>
</template>
//App.vue
<div style="margin: 50px">
<TestVue>
<!-- v-slot:名字="{传递的props}" 使用解构赋值 -->
<template v-slot:slot1="{obj}">
{{ obj }}
</template>
<!-- #名字="{传递的props}" 使用解构赋值 -->
<template #slot2="{num}">
{{ num }}
</template>
<!-- 不使用解构赋值 -->
<template #default="date">
{{ date }}
</template>
</TestVue>
</div>
这里建议大家开发中直接使用解构赋值取值,方便值拿到可以直接用,不用再次进行处理。
使用useSlots来创建动态插槽
有时候在开发中我们并不清楚插槽的数量要多少个,可以在某个区域有时候两个地方需要进行插槽替换,另一个区域有三个地方进行插槽替换。的确是存在这样的场景。就比如在一个复用编辑对话框组件,一个输入框,可能百分之九十用的都是input,有时候却是下拉选择框或是其他,字段名还是一样。那在封装组件的时候不可能说利用if来判断,会增加代码冗余。这时候就需要动态插槽了。
先来说下动态插槽的简单实现
//Test.vue
<script setup>
import { useSlots } from "vue";
const slots = useSlots();
console.log("Test", slots);
</script>
<template>
<el-card class="box-card">
<!-- item为该插槽的渲染方法,key为键名,i为索引 -->
<slot v-for="(item, key, i) in slots" :key="i" :name="key"></slot>
</el-card>
</template>
<template>
<div style="margin: 50px">
<TestVue>
<template v-slot:slot1>
<h1>插槽1</h1>
</template>
<template #slot2>
<h1>插槽2</h1>
</template>
</TestVue>
</div>
</template>
可以看到我在子组件并没有实际定义插槽。而是通过遍历useSlots的实例,然后动态给slot命名。然后在父组件,我可以自定义自己想要的插槽名字,不必受子组件的slot有无名字的限制。
一般来说我们是先子组件定义好插槽,然后父组件替换。但是这个有点反过来,他是父组件书写好后,在组件通过useSlots实例,然后循环创建slot并进行替换。
useSlots主要判断 slot 是否有内容。可以利用这个来判断是否创建插槽
让我们回到前面的所说的业务问题。当你会使用动态插槽后,其实会发现很简单的
//Test.vue
<script setup>
import { useSlots, defineProps, ref } from "vue";
const props = defineProps({
column: {
type: Object,
default: () => {},
},
});
const column = ref(props.column);
const form = ref({})
const slots = useSlots();
console.log("Test", slots);
</script>
<template>
<el-card class="box-card">
<el-form :model="form" label-width="120px">
<!-- 遍历传递过来的数据进行动态渲染。然后利用useSlots实例来判断是否生成了插槽,来是否进行替换 -->
<el-form-item v-for="(item, index) in column" :key="index" :label="item.label">
<!-- 根据命名插槽的特性,我们可以划分区域来进行,这样就可以多区域不限制插槽数量 -->
<slot v-if="slots.hasOwnProperty(`${item?.prop}Form`)" :name="`${item?.prop}Form`"></slot>
<el-input v-else v-model="form[item.prop]" />
</el-form-item>
<el-form-item>
<el-button type="primary">Create</el-button>
<el-button>Cancel</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="box-card">
<el-form :model="form" label-width="120px">
<el-form-item v-for="(item, index) in column" :key="index" :label="item.label">
<slot v-if="slots.hasOwnProperty(`${item?.prop}Search`)" :name="`${item?.prop}Search`"></slot>
<el-input v-else v-model="form[item.prop]" />
</el-form-item>
<el-form-item>
<el-button type="primary">创建</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
//App.vue
<script setup>
import TestVue from "./components/Test.vue";
import { ref, reactive, toRaw } from "vue";
// 定义好表单的属性,传递给子组件
const columnData = [
{
prop: "date",
label: "日期",
},
{
prop: "name",
label: "名字",
},
{
prop: "address",
label: "地址",
},
];
const value1=ref(null)
const value2=ref(null)
</script>
<template>
<div style="margin: 50px">
<TestVue :column="columnData">
<!-- 分区域,子组件那里定义了 字段名+Form和字段名+Search两个区域 -->
<template #dateForm>
<el-date-picker
v-model="value1"
type="datetime"
placeholder="Select date and time"
/>
</template>
<template #dateSearch>
<el-rate v-model="value2" />
</template>
</TestVue>
</div>
</template>
从效果截图看来,我们成功了,两个区域展示不同的组件。而且打印出来的数据也可以得知。只有在父组件定义,才会变为插槽实例。不会多生成无用的实例来占用内存。
最后:这次文章内容分享就到这了,如果觉得这篇文章对你有所收获,能否给小弟点个赞。万分感谢。如果有哪里写得不对或者不好,请积极指出,这样才能继续进步。感谢大家的阅读~(* ̄︶ ̄)