Vue.js: 插槽 slots

最后更新:2026-10-20

插槽(Slot)是 Vue 的内容分发 API——父组件可以往子组件的"槽位"插入任意内容。插槽让组件像"模板"一样灵活:父组件控制显示什么,子组件控制显示在哪里。

Vue 3 提供了 3 种插槽:默认插槽、具名插槽、作用域插槽。本课帮你掌握这 3 种用法和最佳实践。

相关模板与辅助:useSlots()

1. 你将学到


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) 收益

使用插槽后:


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」。

相关模板与辅助:useSlots()

❓ 常见问题

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 时显示。

📖 小节


📝 作业

  1. 基础题(难度⭐) 实现一个简单的 Button 组件:

    • BaseButton.vue:默认插槽 + 3 种 variant(primary/success/danger)
    • props: variant (String)
    • 父组件:测试 3 种 variant + 自定义插槽内容
  2. 进阶题(难度⭐⭐) 实现 BaseLayout 组件:

    • 子组件:3 个具名插槽(header / default / footer)
    • 父组件:使用 BaseLayout 实现 Dashboard 页面
    • 用 $slots 检测插槽是否存在
    • 添加默认内容(fallback)
  3. 挑战题(难度⭐⭐⭐) 实现完整的 DataTable 组件系统:

    1. DataTable.vue:接收 columns + data,渲染表格
    2. 作用域插槽:#cell-{key} 允许父组件自定义单元格
    3. 默认插槽:父组件不提供时显示原始值
    4. 父组件示例:用户表 + 订单表,2 种不同的列渲染
    5. 性能:v-memo 缓存行(数据不变时跳过重渲染)
    6. TypeScript 强类型
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏