一,概念
插槽(Slots)是一种强大的组件复用机制,用于实现组件的内容分发和复用。插槽是组件的特殊占位符,允许父组件向子组件插入自定义内容,其实就是用父组件的自定义内容替换掉这个特殊占位符。通过插槽,子组件可以定义自己的结构,同时允许父组件动态填充部分内容,实现组件的灵活复用。域插槽的核心价值在于将数据逻辑与视图渲染解耦,使组件更加灵活和可复用。通过子组件暴露数据,父组件自定义渲染,既保持了组件的通用性,又满足了个性化需求。在开发通用组件、列表、表单、状态管理等场景中,作用域插槽是必不可少的工具。
二,作用
- 内容分发 子组件通过 标签定义插槽位置,父组件可以在使用子组件时插入具体内容
- 组件复用 相同的子组件可以通过不同的插槽内容实现多样化展示
- 布局和数据解耦 将通用布局逻辑封装在子组件中,具体内容由父组件提供
三,类型
1. 默认插槽(匿名插槽)
子组件中未命名的标签,父组件插入的内容会默认填充到这里
Child.vue
<template>
<h2>我是子组件的标题哈</h2>
<div>
<slot />
</div>
</template>
<script setup></script>
App.vue
<template>
<h1>我是父组件哈</h1>
<child>
<p>我是父组件给插槽的内容</p>
</child>
</template>
<script setup>
import Child from './Child.vue'
</script>
2. 具名插槽
子组件中通过 name 属性命名的插槽,父组件可以通过 v-slot 或 # 语法指定内容填充位置
Child.vue
<template>
<h2>我是子组件呀</h2>
<div>
<slot name='title'></slot><!-- 具名插槽:title -->
</div>
<slot/><!-- 默认插槽 -->
<h3><slot name='content'></slot></h3><!-- 具名插槽:content -->
</template>
<script setup></script>
App.vue
<template>
<h1>我是父组件哈</h1>
<child>
<template #title>
<i>我是父组件给插槽的标题</i>
</template>
<p>我是默认插槽</p>
<template #content>
<i>我是父组件给插槽的内容</i>
</template>
</child>
</template>
<script setup>
import Child from './Child.vue'
</script>
3. 作用域插槽
一般插槽都是父组件提供内容,子组件无法传递数据给父组件用。但借助作用域插槽,父组件能够获取子组件的数据,进而依据这些数据对内容进行动态渲染。这种方式打破了传统插槽只能传递静态内容的局限。
场景1:列表组件的自定义渲染
需求:封装一个通用列表组件,允许父组件根据条件控制每一项的展示方式。子组件负责数据遍历和结构,父组件控制显示。
App.vue
<template>
<h1>我是父组件哈</h1>
<Child :items="users">
<template #default="{ item }">
<div class="user-card">
<h3>{{ item.name }}</h3>
<p>年龄:{{ item.age }}</p>
<p v-if="item.sex === 'M'">性别:{{ item.sex }}</p>
</div>
</template>
</Child>
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const users = ref([
{ id: 1, name: "张三", age: 25 ,sex:'M'},
{ id: 2, name: "李四", age: 30 ,sex:'F'},
{ id: 3, name: "王五", age: 22 ,sex:'M'}
])
</script>
Child.vue
<template>
<ul>
<li v-for="item in items" :key="item.id">
<!-- 通过插槽暴露 item 数据 -->
<slot :item="item"></slot>
</li>
</ul>
</template>
<script setup>
const props = defineProps({
items: {
type: Array,
required: true
}
})
</script>
示例2:表单组件的自定义控件
场景:封装一个表单组件,允许父组件自定义输入控件
可以在父组件中修改input插槽内容,改变表单输入控件
App.vue
<template>
<h1>我是父组件哈</h1>
<Child>
<template #input="{ value, update }">
<!-- 使用自定义输入控件 -->
<input
type="text"
:value="value"
@input="update($event.target.value)"
placeholder="请输入内容"
>
</template>
</Child>
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
</script>
Child.vue
<template>
<form @submit="handleSubmit">
<slot name="input" :value="formValue" :update="updateValue"></slot>
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const formValue = ref('')
const updateValue = (newValue) => {
formValue.value = newValue
}
const handleSubmit = () => {
console.log('提交值:', formValue.value)
}
</script>
四,作用域插槽与普通插槽的对比
五,与$emit对比
作用域插槽和 emit 都能实现子组件向父组件传递数据,不过它们的应用场景和实现方式存在差异。
1.实现机制
- 作用域插槽:子组件把数据以 v-slot 的形式暴露出来,父组件在使用子组件时,可以按需对这些数据进行处理和展示。
- $emit 自定义事件:子组件借助 defineEmits 定义事件,再通过 emit 触发事件并传递数据,父组件监听该事件就能获取到数据。
2.应用场景
- 作用域插槽:适用于子组件已经获取到数据,但要由父组件来决定如何渲染这些数据的情况,例如列表项的渲染。
- $emit 自定义事件:适用于子组件发生某个事件(像点击、输入等)后,需要通知父组件做出响应的场景。