vue中的slot 是一种用于组件内容分发的技术。它允许父组件向子组件传递任意内容,根据插槽的位置显示在任意位置,从而使组件更加灵活和可组合。
作用: 增加了组件的复用性,同时也可以让代码结构更清晰,利于团队协作和组件库的开发。
1 默认插槽
最基本的插槽类型。当使用子组件时,没有指定具体名字的插槽的内容就会被渲染到默认插槽位置。
例子:
xml
<!-- BaseCard.vue -->
<template>
<div class="card">
<header class="card-header">
<h3>卡片标题</h3>
</header>
<section class="card-body">
<!-- 默认插槽 -->
<slot></slot>
</section>
<footer class="card-footer">
卡片底部信息
</footer>
</div>
</template>
<script>
export default {
name: 'BaseCard'
}
</script>
<!-- 使用 BaseCard 的父组件 -->
<template>
<BaseCard>
<p>这是一段使用默认插槽填充的内容。</p>
</BaseCard>
</template>
<script>
import BaseCard from './BaseCard.vue'
export default {
components: { BaseCard }
}
</script>
2 具名插槽
为不同的插槽区域使用name属性命名,父组件在传递内容时可以使用<template #插槽的名字>... </template>
或<template v-slot:插槽的名字>...</template>
来指定父组件传递的内容显示在子组件的位置。
例子:
xml
<!-- ProfileCard.vue -->
<template>
<div class="profile-card">
<div class="profile-avatar">
<slot name="avatar">
<!-- 可选:默认头像 -->
<img src="default-avatar.png" alt="默认头像">
</slot>
</div>
<div class="profile-info">
<slot name="info">
<!-- 可选:默认信息 -->
<p>暂无详细信息</p>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'ProfileCard'
}
</script>
<!-- 使用 ProfileCard 的父组件 -->
<template>
<ProfileCard>
<template v-slot:avatar>
<img src="user-avatar.jpg" alt="用户头像">
</template>
<template v-slot:info>
<h2>张三</h2>
<p>前端开发工程师,热爱分享技术。</p>
</template>
</ProfileCard>
</template>
<script>
import ProfileCard from './ProfileCard.vue'
export default {
components: { ProfileCard }
}
</script>
3 条件插槽
根据判断条件渲染不同插槽,比如v-if...v-else
。
例子:
xml
<!-- StatusCard.vue -->
<template>
<div class="status-card">
<!-- 默认插槽作为 fallback 方案 -->
<slot></slot>
</div>
</template>
<script>
export default {
name: 'StatusCard'
}
</script>
<!-- 使用 StatusCard 的父组件 -->
<template>
<StatusCard>
<template v-if="isLoggedIn">
<p>欢迎回来,{{ username }}!</p>
</template>
<template v-else>
<p>请登录以继续操作。</p>
</template>
</StatusCard>
</template>
<script>
import StatusCard from './StatusCard.vue'
export default {
components: { StatusCard },
data() {
return {
isLoggedIn: false,
username: '张三'
}
}
}
</script>
4 动态插槽
可以理解为具名插槽的升级吧,只是这个插槽的名字是一个变量,会变。通过这个插槽名字的变化,他就能在子组件的固定位置动态显示父组件传过来的不同插槽内容。
例子:
假设你有一个 DynamicTab
组件,根据选中的标签动态展示内容
xml
<!-- DynamicTab.vue -->
<template>
<div class="dynamic-tab">
<div class="tab-header">
<button v-for="(tab, index) in tabs" :key="index" @click="activeTab = tab.name">
{{ tab.label }}
</button>
</div>
<div class="tab-content">
<!-- 动态插槽,根据 activeTab 来决定显示哪个 slot -->
<slot :name="activeTab"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'DynamicTab',
data() {
return {
activeTab: 'tab1',
tabs: [
{ name: 'tab1', label: '标签 1' },
{ name: 'tab2', label: '标签 2' },
{ name: 'tab3', label: '标签 3' }
]
}
}
}
</script>
<style scoped>
.tab-header button { margin-right: 8px; }
.tab-content { border: 1px solid #ccc; padding: 16px; margin-top: 8px; }
</style>
在父组件中使用时,通过 v-slot
将每个标签对应的内容关联到正确的名称上:
xml
<!-- 使用 DynamicTab 的父组件 -->
<template>
<DynamicTab>
<template v-slot:tab1>
<p>这里是标签 1 的内容。</p>
</template>
<template v-slot:tab2>
<p>这里是标签 2 的内容。</p>
</template>
<template v-slot:tab3>
<p>这里是标签 3 的内容。</p>
</template>
</DynamicTab>
</template>
<script>
import DynamicTab from './DynamicTab.vue'
export default {
components: { DynamicTab }
}
</script>
5 作用域插槽
作用域插槽(scoped slot)允许子组件向插槽内容传递数据或方法,从而让父组件在使用插槽时能获得子组件的局部状态或计算结果。 也就是说父组件里面使用子组件的插槽时,可以通过插槽获取到子组件传过来的值。
5.1. 常规用法
例子:
假设我们有一个 ListDisplay
组件,用于展示一组列表数据,并将每个列表项的数据通过作用域插槽传递给父组件以便自定义渲染
xml
<!-- ListDisplay.vue -->
<template>
<ul>
<li v-for="(item, index) in items" :key="index">
<!-- 将 item 作为 slotProps 传递出去 -->
<slot :item="item"></slot>
</li>
</ul>
</template>
<script>
export default {
name: 'ListDisplay',
props: {
items: {
type: Array,
default: () => []
}
}
}
</script>
在父组件中,我们通过作用域插槽接收子组件传来的 item
数据,并灵活地自定义列表项的展示形式:
xml
<!-- 使用 ListDisplay 的父组件 -->
<template>
<ListDisplay :items="['苹果', '香蕉', '橘子']">
<template v-slot:default="slotProps">
<strong>{{ slotProps.item }}</strong> ------ 好吃的水果!
</template>
</ListDisplay>
</template>
<script>
import ListDisplay from './ListDisplay.vue'
export default {
components: { ListDisplay }
}
</script>
5.2. 进阶用法:传递多个数据
作用域插槽不仅可以传递单个数据,也可以传递多个数据或方法。例如,一个图表组件可能需要传递数值和格式化方法:
xml
<!-- ChartDisplay.vue -->
<template>
<div class="chart">
<slot :data="chartData" :format="formatValue"></slot>
</div>
</template>
<script>
export default {
name: 'ChartDisplay',
data() {
return {
chartData: [10, 20, 30, 40]
}
},
methods: {
formatValue(value) {
return `${value} 单位`
}
}
}
</script>
<!-- 使用 ChartDisplay 的父组件 -->
<template>
<ChartDisplay>
<template v-slot:default="slotProps">
<ul>
<li v-for="(val, index) in slotProps.data" :key="index">
{{ slotProps.format(val) }}
</li>
</ul>
</template>
</ChartDisplay>
</template>
<script>
import ChartDisplay from './ChartDisplay.vue'
export default {
components: { ChartDisplay }
}
</script>
6. 举一反三:插槽混合使用
下面这个是一个基于 Vue 3+ 组合式 API 语法编写的综合示例,包含默认插槽、具名插槽、条件插槽和作用域插槽。示例中定义了一个 UserCard 组件,以及一个父组件 ParentComponent,父组件在使用 UserCard 时灵活定制各个插槽的内容。
子组件UserCard.vue:
这个组件是使用vue3+组合式 API(通过 <script setup>
语法)来定义 props 和内部方法,把多种插槽结合在一起灵活使用,同时包含:
- 具名插槽
**header**
:可自定义头部信息,默认显示用户姓名; - 默认插槽:显示主体内容,默认展示用户年龄;
- 条件插槽
**premium**
:当user.isPremium
为true
时才展示高级会员区域(可被父组件覆盖); - 作用域插槽
**actions**
:将用户数据传递到父组件,父组件可自定义操作按钮。、
xml
<!-- UserCard.vue -->
<template>
<div class="user-card" style="border:1px solid #ccc; padding:16px; border-radius:4px;">
<!-- 具名插槽:header -->
<header style="margin-bottom:8px;">
<slot name="header">
<h2>{{ user.name }}</h2>
</slot>
</header>
<!-- 默认插槽:主体内容 -->
<div class="user-content" style="margin-bottom:8px;">
<slot>
<p>年龄:{{ user.age }}</p>
</slot>
</div>
<!-- 条件插槽:premium -->
<div v-if="user.isPremium" class="premium-section" style="margin-bottom:8px; color: darkorange;">
<slot name="premium">
<p>高级会员</p>
</slot>
</div>
<!-- 作用域插槽:actions,将 user 作为属性传递出去 -->
<footer>
<slot name="actions" :user="user">
<button @click="defaultAction">默认操作</button>
</slot>
</footer>
</div>
</template>
<script setup>
const props = defineProps({
user: {
type: Object,
required: true
}
})
function defaultAction() {
alert('默认操作被触发!')
}
</script>
父组件 ParentComponent.vue:
父组件使用组合式 API,通过 <script setup>
导入并使用 UserCard 组件,同时对各个插槽进行定制:
- 为具名插槽
header
提供自定义头部显示; - 默认插槽中覆盖了组件内部提供的主体内容;
- 条件插槽
premium
被父组件自定义显示高级会员信息; - 作用域插槽
actions
接收了传递的user
数据,自定义一个操作按钮。
xml
<!-- ParentComponent.vue -->
<template>
<UserCard :user="user">
<!-- 具名插槽:header 自定义头部显示 -->
<template v-slot:header>
<h2 style="color: teal;">自定义头部:{{ user.name }}</h2>
</template>
<!-- 默认插槽:自定义主体内容 -->
<p>这是 {{ user.name }} 的详细介绍,年龄:{{ user.age }},充满活力!</p>
<!-- 条件插槽:premium 自定义显示 -->
<template v-slot:premium>
<p style="font-weight: bold;">欢迎高级会员 {{ user.name }},尊享专属特权!</p>
</template>
<!-- 作用域插槽:actions,根据传入的 user 数据自定义操作按钮 -->
<template v-slot:actions="slotProps">
<button @click="handleAction(slotProps.user)" style="background-color: lightblue; padding: 4px 8px;">
为 {{ slotProps.user.name }} 执行操作
</button>
</template>
</UserCard>
</template>
<script setup>
import UserCard from './UserCard.vue'
import { reactive } from 'vue'
// 使用 reactive 定义用户数据
const user = reactive({
name: '张三',
age: 28,
isPremium: true // 修改为 false 可测试条件插槽不显示的情况
})
function handleAction(user) {
alert(`为 ${user.name} 执行了定制操作!`)
}
</script>
7 小结
ok,vue的所有插槽在上面已经总结完毕,可能在我们开发中默认插槽、具名插槽、条件插槽会用的多一些,复杂的组件封装时作用域插槽也会有所使用,动态插槽用的少些,可能是我使用的少。知识点就是上面这些,更多的是在我们日常开发中要灵活使用、融会贯通。