在 Vue 开发中,你是否遇到过这样的需求?
"我想让组件的某一部分由使用者自定义内容,而不是写死在组件内部。"
这就是 Slot(插槽) 的核心价值。
本文将深入讲解 Vue 插槽的三类用法 、核心原理 和实际应用场景,助你打造真正灵活的可复用组件。
一、什么是 Slot?
✅ 定义
Slot (插槽)是 Vue 的内容分发机制 ,允许父组件向子组件传递模板内容,并由子组件决定这些内容的插入位置和渲染方式。
📌 类比理解
想象一个 HTML <select>
元素:
html
<select>
<option>苹果</option>
<option>香蕉</option>
<option>橙子</option>
</select>
<select>
是组件(容器);<option>
是由使用者提供的内容;<select>
决定<option>
如何渲染。
这就是"内容分发"的思想。
二、三类插槽详解
1️⃣ 默认插槽(Default Slot)
✅ 特点
- 无
name
属性; - 每个组件最多一个;
- 接收父组件传递的默认内容。
📌 示例:卡片组件
vue
<!-- 子组件:Card.vue -->
<template>
<div class="card">
<header>卡片标题</header>
<main>
<slot></slot> <!-- 默认插槽出口 -->
</main>
<footer>底部信息</footer>
</div>
</template>
vue
<!-- 父组件使用 -->
<card>
<p>这是卡片的主体内容,由父组件定义。</p>
</card>
🔍 原理
- 子组件渲染时,
<slot>
标签会被替换为父组件传递的模板片段; - 该片段存储在
vm.$slots.default
中。
2️⃣ 具名插槽(Named Slot)
✅ 特点
- 通过
name
属性标识; - 一个组件可有多个具名插槽;
- 父组件通过
v-slot:name
指定内容。
📌 示例:布局组件
vue
<!-- Layout.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
vue
<!-- 父组件使用 -->
<layout>
<!-- 具名插槽 -->
<template v-slot:header>
<h1>页面标题</h1>
</template>
<!-- 默认插槽 -->
<p>页面主体内容</p>
<!-- 具名插槽(简写) -->
<template #footer>
<small>© 2025</small>
</template>
</layout>
🔍 原理
- 具名插槽内容存储在
vm.$slots
对象中:vm.$slots.header
vm.$slots.footer
vm.$slots.default
3️⃣ 作用域插槽(Scoped Slot)
✅ 特点
- 子组件可向父组件传递数据;
- 父组件根据子组件的数据决定如何渲染;
- 实现"反向控制"(Inversion of Control)。
📌 示例:列表组件
vue
<!-- 子组件:UserList.vue -->
<template>
<ul>
<li v-for="user in users" :key="user.id">
<!-- 将 user 数据传递给父组件 -->
<slot :user="user" :index="index">
<!-- 默认内容 -->
{{ user.name }}
</slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 }
]
};
}
};
</script>
vue
<!-- 父组件使用 -->
<user-list>
<template v-slot:default="slotProps">
<!-- 父组件使用子组件传递的数据 -->
<strong>{{ slotProps.user.name }}</strong>
({{ slotProps.user.age }}岁)
</template>
</user-list>
🔍 原理
- 子组件在渲染时,将数据作为
<slot>
标签的属性传递; - 父组件通过
v-slot:xxx="props"
接收; - 本质是将子组件的数据暴露给父组件的作用域。
💡 简写技巧
vue
<!-- 全写 -->
<template v-slot:default="slotProps">...</template>
<!-- 简写(推荐) -->
<template #default="{ user, index }">
<strong>{{ user.name }}</strong>
</template>
<!-- 甚至可以省略 #default -->
<template #default="{ user }">...</template>
三、插槽的实现原理
✅ 编译阶段
- Vue 编译器解析模板;
- 遇到
<slot>
标签,生成对应的渲染函数; - 父组件的插槽内容被编译为作用域函数 (
render function
)。
✅ 运行时
-
子组件实例化时:
-
收集父组件传递的插槽内容;
-
存储在
vm.$slots
中:jsvm.$slots = { default: [VNode], // 默认插槽 header: [VNode], // 具名插槽 footer: [VNode] }
-
若是作用域插槽,数据绑定在
slot
标签上。
-
-
渲染阶段:
- 遇到
<slot>
标签; - 查找
vm.$slots
对应的 VNode; - 若是作用域插槽,执行作用域函数并传入数据;
- 替换
<slot>
标签。
- 遇到
📌 原理图解
bash
父组件
↓ 传递模板 + 数据
子组件
↓ 存入 vm.$slots
↓ 渲染时替换 <slot>
最终 DOM
四、最佳实践与注意事项
✅ 使用建议
场景 | 推荐插槽类型 |
---|---|
组件主体内容可定制 | 默认插槽 |
多区域布局(头/尾/侧边栏) | 具名插槽 |
列表项、表格单元格自定义渲染 | 作用域插槽 |
❌ 常见误区
1. 混淆 slot
和 props
vue
<!-- ❌ 错误:用 props 传递大段 HTML -->
<card :content="<p>自定义内容</p>"></card>
<!-- ✅ 正确:使用 slot -->
<card>
<p>自定义内容</p>
</card>
2. 忘记作用域插槽的解构
vue
<!-- ❌ 冗长 -->
<template v-slot:default="props">
{{ props.user.name }}
</template>
<!-- ✅ 推荐 -->
<template #default="{ user }">
{{ user.name }}
</template>
💡 结语
"Slot 是 Vue 组件灵活性的灵魂。"
- 默认插槽:最简单的定制入口;
- 具名插槽:多区域内容分发;
- 作用域插槽:数据反向传递,实现高度可定制。
掌握插槽,你就能写出像 Element UI
、Ant Design Vue
那样强大而灵活的组件库。