一、基本插槽(默认插槽)
父组件传递内容
vue
<!-- Parent.vue -->
<template>
<ChildComponent>
<!-- 这里的内容会被插入到子组件的 slot 位置 -->
<p>这是从父组件传入的内容</p>
</ChildComponent>
</template>
子组件定义插槽位置
vue
<!-- ChildComponent.vue -->
<template>
<div class="card">
<h2>卡片标题</h2>
<!-- slot 就是内容的占位符 -->
<slot></slot>
</div>
</template>
渲染结果:
html
<div class="card">
<h2>卡片标题</h2>
<p>这是从父组件传入的内容</p>
</div>
二、具名插槽
当一个组件有多个插槽时,需要用名字来区分。
子组件定义多个命名插槽
vue
<!-- Layout.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<!-- 没有 name 的就是默认插槽 -->
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
父组件使用具名插槽
vue
<!-- App.vue -->
<template>
<Layout>
<!-- v-slot 指令指定插槽名,简写为 # -->
<template #header>
<h1>页面标题</h1>
</template>
<!-- 默认插槽不需要指定名称 -->
<p>主要内容区域</p>
<template #footer>
<p>版权信息 © 2026</p>
</template>
</Layout>
</template>
语法说明:
v-slot:header可简写为#header- 默认插槽可以省略
template标签,直接写在组件标签内 - 一个组件只能有一个默认插槽
三、作用域插槽
让父组件能访问子组件内部的数据。
子组件向插槽传递数据
vue
<!-- List.vue -->
<template>
<ul>
<li v-for="(item, index) in items" :key="index">
<!-- 通过 v-bind 将数据绑定到 slot 上 -->
<slot :item="item" :index="index" :isEven="index % 2 === 0">
<!-- 默认显示内容,当父组件未提供插槽内容时显示 -->
{{ item }}
</slot>
</li>
</ul>
</template>
<script setup>
const props = defineProps({
items: Array
})
</script>
父组件接收并使用插槽数据
vue
<!-- Parent.vue -->
<template>
<List :items="['苹果', '香蕉', '橘子']">
<!-- 通过 v-slot 接收子组件传来的数据 -->
<template #default="{ item, index, isEven }">
<span :class="{ even: isEven }">
{{ index + 1 }}. {{ item }}
</span>
</template>
</List>
</template>
解构语法 :#default="{ item, index }" 可以直接解构出需要的属性,也可以设置默认值 #default="{ item = '未知' }"。
四、动态插槽名
Vue 3 支持用变量作为插槽名称。
vue
<template>
<BaseLayout>
<template #[dynamicSlotName]>
动态插槽内容
</template>
</BaseLayout>
</template>
<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header')
</script>
五、插槽的默认内容
可以在 <slot> 标签内放置默认内容,当父组件没有提供插槽内容时显示。
vue
<!-- Button.vue -->
<template>
<button class="btn">
<slot>提交</slot> <!-- 默认显示"提交" -->
</button>
</template>
vue
<!-- 使用1:不传内容,显示默认 -->
<MyButton /> <!-- 渲染为 <button>提交</button> -->
<!-- 使用2:传入内容,覆盖默认 -->
<MyButton>保存</MyButton> <!-- 渲染为 <button>保存</button> -->
六、Vue 3 相比 Vue 2 的变化
1. 统一了 slot 和 slot-scope 语法
Vue 2 中有两套语法(slot / slot-scope 属性和 v-slot 指令),Vue 3 只保留 v-slot 指令。
2. 移除 $scopedSlots
Vue 2 中需要通过 this.$scopedSlots 访问作用域插槽,Vue 3 统一合并到 this.$slots。
3. 渲染函数中的变化
javascript
// Vue 2
h('div', [
this.$scopedSlots.default({ text: 'hello' })
])
// Vue 3
h('div', [
this.$slots.default({ text: 'hello' })
])
七、实际应用示例:表格列自定义
vue
<!-- DataTable.vue -->
<template>
<table>
<thead>
<tr>
<th v-for="col in columns" :key="col.key">{{ col.title }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<td v-for="col in columns" :key="col.key">
<!-- 允许用户自定义某列的渲染方式 -->
<slot :name="`cell-${col.key}`" :row="row" :value="row[col.key]">
{{ row[col.key] }}
</slot>
</td>
</tr>
</tbody>
</table>
</template>
<script setup>
defineProps({
columns: Array,
data: Array
})
</script>
vue
<!-- 使用表格并自定义操作列 -->
<DataTable :columns="columns" :data="users">
<template #cell-action="{ row }">
<button @click="editUser(row)">编辑</button>
<button @click="deleteUser(row)">删除</button>
</template>
</DataTable>
总结:Vue 3 的插槽机制更加简洁统一,作用域插槽让父子组件间的数据传递更加灵活,非常适合构建可复用的通用组件库。掌握插槽是写好 Vue 组件的重要基础。