Vue.js: 插槽 slots
最后更新:2026-10-20
插槽(Slot)是 Vue 的内容分发 API——父组件可以往子组件的"槽位"插入任意内容。插槽让组件像"模板"一样灵活:父组件控制显示什么,子组件控制显示在哪里。
Vue 3 提供了 3 种插槽:默认插槽、具名插槽、作用域插槽。本课帮你掌握这 3 种用法和最佳实践。
1. 你将学到
- 默认插槽
<slot />的基本用法 - 具名插槽
<slot name="header" />和v-slot:header简写 - 作用域插槽:子组件传数据给父组件插槽
<template v-slot>语法(Vue 2.6+)- 动态插槽名和缩写语法
- 默认插槽内容(fallback content)
$slots/useSlots钩子
2. 一个 Card 组件无法定制内容的难题
(1) 痛点:5 种 Card 样式,要写 5 个组件
Alice 的电商后台需要 5 种卡片:
VUE
<!-- ❌ 翻车版:5 个组件 -->
<!-- ProductCard.vue -->
<div class="card">{{ product.name }}</div>
<!-- UserCard.vue -->
<div class="card">{{ user.name }}</div>
<!-- OrderCard.vue -->
<div class="card">Order #{{ order.id }}</div>
<!-- 5 个组件,90% 代码相同,只有内容不同 -->
产品经理 Charlie:
"Alice,下周还要加 5 种卡片类型。不能一直加新组件。我们需要一个灵活的 Card,能接收任何内容。"
(2) Vue 插槽解法:1 个 BaseCard,父组件塞内容
1 个 BaseCard.vue → 无限种卡片。每次新增只需写内容,不用改组件。
(3) 收益
使用插槽后:
- 组件数:5 → 1(-80%)
- 新增卡片类型:1 行模板代码
- 维护成本:改 1 处样式,5+ 卡片同步更新
- 可复用性:BaseCard 可用于所有卡片场景
3. 默认插槽
(1) 基本用法
渲染结果:
HTML
<div class="card">
<h3>Hello</h3>
<p>This is the card content</p>
</div>
(2) 默认内容(fallback)
VUE
<!-- 父组件不传内容 -->
<BaseCard />
<!-- 渲染:<p>No content provided</p> -->
<!-- 父组件传内容 -->
<BaseCard>
<p>Custom content</p>
</BaseCard>
<!-- 渲染:<p>Custom content</p>(覆盖默认)-->
(3) 5 大使用场景
| 场景 | 用法 |
|---|---|
| 卡片内容 | <slot /> |
| 按钮文字 | <slot /> |
| 列表项 | <slot :item="item" /> |
| 表单字段 | <slot /> |
| 模态框主体 | <slot /> |
4. 具名插槽
(1) 定义具名插槽
(2) 父组件使用(3 种语法)
(3) 动态插槽名
(4) 检测插槽存在
5. 作用域插槽
(1) 核心:子组件传数据给父组件插槽
渲染结果:
HTML
<ul>
<li><p>1. Alice (alice@example.com)</p></li>
<li><p>2. Bob (bob@example.com)</p></li>
<li><p>3. Charlie (charlie@example.com)</p></li>
</ul>
(2) 5 种作用域插槽模式
| 模式 | 父组件写法 | 用途 |
|---|---|---|
| 接收所有 props | <template #default="slotProps"> |
接收整个对象 |
| 解构 | <template #default="{ user }"> |
只用需要的字段 |
| 重命名 | <template #default="{ user: u }"> |
避免变量冲突 |
| 默认值 | <template #default="{ user = defaultUser }"> |
解构时给默认值 |
| 不用 template | {{ slotProps.user.name }} |
简单场景(默认插槽) |
(3) 完整示例:可定制列表
▶ 示例:作用域插槽(DataTable 自定义列)
▶ 展开运行示例(71 行)
输出:
TEXT
表格表头 ID / Name / Email / Status / Actions,下面两行数据:Alice(绿色 active 徽章)、Bob(灰色 inactive 徽章),每行末尾一个蓝色「Edit」按钮。
点击 Edit 只在控制台打印「Edit: Alice」,页面内容不变。
(1) 📊 5 种 BaseCard 使用方式速查(纯展示)
| 方式 | 父组件模板 | 用途 |
|---|---|---|
| 1. 最简单(只传 body) | <BaseCard><p>Simple</p></BaseCard> |
只用默认插槽 |
| 2. 带 header | <BaseCard><template #header>...</template>...</BaseCard> |
加头部 |
| 3. 完整 header + body + footer | 三段 <template #name> |
完整布局 |
| 4. 完整写法(v-slot) | <template v-slot:header> |
长写法(旧文档) |
| 5. 作用域插槽 | <template #actions="{ user }"> |
子→父传数据 |
(2) 📊 5 个常见错误速查
| 错误 | 现象 | 解决 |
|---|---|---|
| 插槽名拼错 | 内容不显示 | 检查 <slot name="..."> 和 #name 一致 |
| 父组件多个根元素 | 警告 | 父组件用 <template #name> 包装 |
| v-slot 旧语法 | Vue 2 兼容 | 改用 v-slot:header 或 #header |
| 默认插槽没传 | 父组件不显示 | 检查父组件的 <Component /> 内容 |
| 作用域插槽不解构 | 模板冗长 | 用 { user, index } 解构 |
(3) 📊 5 个性能对比
| 写法 | 编译 | 性能 | 推荐 |
|---|---|---|---|
<slot /> |
静态 | ⭐⭐⭐⭐⭐ | 默认 |
<slot name="x" /> |
静态 | ⭐⭐⭐⭐⭐ | 具名 |
<slot :x="x" /> |
静态 | ⭐⭐⭐⭐ | 作用域 |
v-if="$slots.x" |
条件 | ⭐⭐⭐⭐ | 检测存在 |
useSlots() |
运行时 | ⭐⭐⭐ | JS 中用 |
6. $slots 和 useSlots
(1) $slots 访问插槽
(2) useSlots(Composition API)
VUE
<script setup>
const { useSlots } = Vue
const slots = useSlots()
// 检查插槽是否存在
if (slots.header) {
console.log('Header slot exists')
}
// 动态渲染
if (slots.default) {
console.log('Default slot exists')
}
</script>
(3) $slots vs useSlots
| 维度 | $slots |
useSlots() |
|---|---|---|
| API 类型 | 模板中直接用 | JS 中用(Composition API) |
| 返回 | 插槽对象 | 插槽对象(refs 形式) |
| 场景 | template 内 | <script setup> 内 |
▶ 示例:$slots 5 大用途
▶ 试一试输出:
TEXT
灰边圆角容器内三段内容:浅灰页头「Header 内容」、正文「默认插槽:主要内容」、浅灰页脚「Footer 内容」。
容器底部灰色小字「已检测插槽: header, default, footer」。
7. 完整示例:可定制 Card 组件
▶ 示例:BaseCard 默认 + 具名插槽
▶ 试一试输出:
TEXT
三张卡片自上而下:
1) 只有正文「Simple content (only body)」,没有灰色页头和页脚;
2) 灰色页头「Product Name」+ 正文「Product description」;
3) 页头「Order #12345」+ 正文「Total: $99.99」+ 页脚绿色按钮「View Details」。
❓ 常见问题
Q 插槽和 props 有什么区别?
A props 传数据(任何 JS 值),插槽传内容(HTML/组件片段)。Props 适合配置项,插槽适合内容定制。
Q 作用域插槽什么时候用?
A 当父组件需要基于子组件的数据自定义渲染时。例如:表格的列渲染、列表的项渲染、卡片的内容布局。
Q v-slot 和 # 缩写区别?
A 完全等价,
#header 是 v-slot:header 的简写。推荐用缩写(更简洁)。Q 默认插槽可以命名吗?
A 默认插槽不需要命名,
<slot /> 就是默认。如果非要命名,用 <slot name="default" />,父组件用 <template #default> 或直接传内容。Q 插槽能在 props 中传递吗?
A 不能直接 prop 插槽。但可以 prop 一个 VNode / Render 函数(高级用法)。普通场景用
<slot> 即可。Q 怎么在父组件中检测子组件的插槽?
A 用
$slots(template)或 useSlots()(JS)。子组件用 <slot> 暴露,父组件用 <template #name> 填充。Q 具名插槽可以有默认内容吗?
A 可以。
<slot name="header"><h3>Default Header</h3></slot>。当父组件不传 header 时显示。📖 小节
- 插槽是 Vue 的内容分发 API:父组件控制内容,子组件控制位置
- 3 种插槽:默认插槽(
<slot />)、具名插槽(name="x")、作用域插槽(:x="x") - 父组件用
<template #name>或缩写#name传递具名插槽 - 作用域插槽让子组件传数据给父组件(slot props)
<slot>默认内容(fallback)当父组件不传时显示$slots/useSlots()检查插槽存在- 插槽和 props 配合:props 传配置,插槽传内容
📝 作业
-
基础题(难度⭐) 实现一个简单的 Button 组件:
- BaseButton.vue:默认插槽 + 3 种 variant(primary/success/danger)
- props:
variant(String) - 父组件:测试 3 种 variant + 自定义插槽内容
-
进阶题(难度⭐⭐) 实现 BaseLayout 组件:
- 子组件:3 个具名插槽(header / default / footer)
- 父组件:使用 BaseLayout 实现 Dashboard 页面
- 用
$slots检测插槽是否存在 - 添加默认内容(fallback)
-
挑战题(难度⭐⭐⭐) 实现完整的 DataTable 组件系统:
- DataTable.vue:接收 columns + data,渲染表格
- 作用域插槽:
#cell-{key}允许父组件自定义单元格 - 默认插槽:父组件不提供时显示原始值
- 父组件示例:用户表 + 订单表,2 种不同的列渲染
- 性能:v-memo 缓存行(数据不变时跳过重渲染)
- TypeScript 强类型