文章目录
- 前言
- 一、为什么需要插槽
-
- [1.1 问题](#1.1 问题)
- [1.2 插槽的作用](#1.2 插槽的作用)
- 二、默认插槽
-
- [2.1 基本用法](#2.1 基本用法)
- [2.2 默认内容](#2.2 默认内容)
- 三、具名插槽
-
- [3.1 定义](#3.1 定义)
- [3.2 父组件使用](#3.2 父组件使用)
- [3.3 简写规则](#3.3 简写规则)
- 四、作用域插槽
-
- [4.1 定义](#4.1 定义)
- [4.2 父组件使用](#4.2 父组件使用)
- [4.3 具名 + 作用域插槽](#4.3 具名 + 作用域插槽)
- 五、应用场景
-
- [5.1 通用卡片组件](#5.1 通用卡片组件)
- [5.2 布局组件](#5.2 布局组件)
- [5.3 表格自定义列](#5.3 表格自定义列)
- [5.4 对话框组件](#5.4 对话框组件)
- [六、Vue 2 vs Vue 3 语法](#六、Vue 2 vs Vue 3 语法)
-
- [6.1 变化对照](#6.1 变化对照)
- [6.2 注意事项](#6.2 注意事项)
- 七、面试聚焦
-
- [7.1 插槽内容的编译作用域](#7.1 插槽内容的编译作用域)
- [7.2 作用域插槽 vs 普通插槽](#7.2 作用域插槽 vs 普通插槽)
- [7.3 Vue 3 移除 slot-scope](#7.3 Vue 3 移除 slot-scope)
- 八、易混淆点
- 九、思考与练习
- 总结
前言
插槽(Slot)是 Vue 的内容分发机制,让父组件可以向子组件模板中注入任意内容。本篇会讲清楚:
- 默认插槽、具名插槽、作用域插槽
v-slot/#简写- 插槽内容的编译作用域
- Vue 2 到 Vue 3 的语法变化
一、为什么需要插槽
1.1 问题
vue
<!-- 子组件 Card 只能写死内容 -->
<template>
<div class="card">
<h3>固定标题</h3>
<p>固定内容</p>
</div>
</template>
<!-- 父组件无法自定义卡片内部内容 -->
<Card />
1.2 插槽的作用
插槽允许父组件向子组件注入自定义内容,子组件只负责布局和容器,内容由父组件决定。
vue
<!-- 子组件 Card.vue -->
<template>
<div class="card">
<slot>默认内容</slot>
</div>
</template>
<!-- 父组件 -->
<Card>
<h3>自定义标题</h3>
<p>自定义内容</p>
</Card>
二、默认插槽
2.1 基本用法
vue
<!-- 子组件 MyButton.vue -->
<template>
<button class="btn">
<slot>默认按钮文字</slot>
</button>
</template>
<!-- 父组件 -->
<MyButton>提交</MyButton>
<!-- 渲染:<button class="btn">提交</button> -->
<MyButton />
<!-- 渲染:<button class="btn">默认按钮文字</button> -->
2.2 默认内容
<slot> 标签内的内容是后备内容,父组件未传入内容时显示:
vue
<slot>
<span>暂无内容</span>
</slot>
三、具名插槽
3.1 定义
具名插槽通过 name 属性区分多个插槽,父组件使用 v-slot:name 或 #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>
3.2 父组件使用
vue
<template>
<Layout>
<!-- 具名插槽:v-slot:header 或 #header -->
<template #header>
<h1>页面标题</h1>
</template>
<!-- 默认插槽 -->
<p>主要内容区域</p>
<template #footer>
<p>© 2026 版权所有</p>
</template>
</Layout>
</template>
3.3 简写规则
vue
<!-- 完整写法 -->
<template v-slot:header>标题</template>
<!-- 简写(推荐) -->
<template #header>标题</template>
<!-- 默认插槽简写 -->
<template #default>内容</template>
<!-- 或直接写内容,省略 template -->
<Layout>内容</Layout>
四、作用域插槽
4.1 定义
作用域插槽将子组件的数据通过 props 传递给父组件,让父组件控制渲染逻辑。
vue
<!-- 子组件 List.vue -->
<script setup>
defineProps({ items: Array })
</script>
<template>
<ul>
<li v-for="(item, index) in items" :key="item.id">
<!-- 向父组件暴露 item 数据 -->
<slot :item="item" :index="index">
{{ item.name }}
</slot>
</li>
</ul>
</template>
4.2 父组件使用
vue
<template>
<!-- 接收子组件传递的数据 -->
<List :items="list" v-slot="{ item }">
<b>{{ item.name }}</b> - {{ item.desc }}
</List>
<!-- 解构多个 prop -->
<List :items="list" v-slot="{ item, index }">
{{ index + 1 }}. {{ item.name }}
</List>
</template>
4.3 具名 + 作用域插槽
vue
<!-- 子组件 Table.vue -->
<script setup>
defineProps({
data: { type: Array, required: true },
columns: { type: Array, required: true }
})
</script>
<template>
<table>
<thead>
<tr>
<!-- 向 #header 插槽暴露 columns -->
<slot name="header" :columns="columns"></slot>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<!-- 向 #row 插槽暴露当前行数据 -->
<slot name="row" :row="row"></slot>
</tr>
</tbody>
</table>
</template>
vue
<!-- 父组件 -->
<script setup>
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
]
const columns = ['姓名', '邮箱']
</script>
<template>
<Table :data="users" :columns="columns">
<template #header="{ columns }">
<th v-for="col in columns" :key="col">{{ col }}</th>
</template>
<template #row="{ row }">
<td>{{ row.name }}</td>
<td>{{ row.email }}</td>
</template>
</Table>
</template>
说明:
- 子组件通过
defineProps接收data和columns #header插槽接收子组件传出的columns,渲染表头#row插槽在每一行循环中接收row,渲染单元格
五、应用场景
5.1 通用卡片组件
vue
<!-- Card.vue -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">默认标题</slot>
</div>
<div class="card-body">
<slot>默认内容</slot>
</div>
</div>
</template>
<!-- 使用 -->
<Card>
<template #header>用户信息</template>
<p>姓名:Alice</p>
</Card>
5.2 布局组件
vue
<!-- PageLayout.vue -->
<template>
<div class="page">
<aside><slot name="sidebar" /></aside>
<main><slot /></main>
</div>
</template>
5.3 表格自定义列
vue
<!-- DataTable.vue -->
<template>
<table>
<tr v-for="row in rows" :key="row.id">
<td v-for="col in columns" :key="col.key">
<slot :name="col.key" :row="row" :value="row[col.key]">
{{ row[col.key] }}
</slot>
</td>
</tr>
</table>
</template>
<!-- 父组件自定义某列渲染 -->
<DataTable :rows="data" :columns="cols">
<template #status="{ value }">
<span :class="value">{{ value }}</span>
</template>
</DataTable>
5.4 对话框组件
vue
<!-- Dialog.vue -->
<template>
<div class="dialog">
<div class="dialog-header">
<slot name="title">提示</slot>
</div>
<div class="dialog-body">
<slot />
</div>
<div class="dialog-footer">
<slot name="footer">
<button @click="$emit('close')">关闭</button>
</slot>
</div>
</div>
</template>
六、Vue 2 vs Vue 3 语法
6.1 变化对照
| Vue 2 | Vue 3 |
|---|---|
slot="header" |
#header 或 v-slot:header |
slot-scope="{ item }" |
#default="{ item }" |
| 具名 + 作用域分开写 | 统一用 v-slot |
vue
<!-- Vue 2 -->
<List :items="list">
<template slot="item" slot-scope="{ item }">
{{ item.name }}
</template>
</List>
<!-- Vue 3 -->
<List :items="list">
<template #item="{ item }">
{{ item.name }}
</template>
</List>
6.2 注意事项
vue
<!-- ❌ v-slot 不能直接用在普通元素上 -->
<div #header>标题</div>
<!-- ✅ 必须用在 template 上 -->
<template #header>标题</template>
<!-- ✅ 默认插槽可以省略 template -->
<MyComponent>直接写内容</MyComponent>
七、面试聚焦
7.1 插槽内容的编译作用域
vue
<!-- 插槽内容在父组件作用域中编译 -->
<script setup>
const parentMsg = '来自父组件'
</script>
<template>
<Child>
{{ parentMsg }} <!-- ✅ 可以访问父组件数据 -->
<!-- {{ childMsg }} ❌ 无法访问子组件数据(除非作用域插槽) -->
</Child>
</template>
7.2 作用域插槽 vs 普通插槽
javascript
// 普通插槽:父组件向子组件注入内容
// 作用域插槽:子组件向父组件暴露数据,父组件决定如何渲染
// 典型场景:表格/List 组件暴露 row 数据,父组件自定义列渲染
7.3 Vue 3 移除 slot-scope
javascript
// Vue 2: slot + slot-scope 两个属性
// Vue 3: 统一 v-slot(简写 #),语法更一致
八、易混淆点
- 插槽内容在父组件作用域编译:只能访问父组件的数据和方法,不能直接访问子组件内部数据。
- 作用域插槽的数据是只读的:父组件不能通过插槽 props 修改子组件内部状态。
- v-slot 只能用在
<template>上:默认插槽内容可以直接写,具名/作用域插槽需用 template。 - Vue 3 移除 slot / slot-scope :统一使用
v-slot或#简写。 - 默认插槽名是 default :
<slot>等价于<slot name="default">。
九、思考与练习
1. 插槽和普通 Props 传递 HTML 有什么区别?
解析:
- Props:传递数据,子组件控制如何渲染
- 插槽:传递内容/结构,父组件控制渲染什么
2. 作用域插槽和普通插槽的区别?
解析:
- 普通插槽:父 → 子,父组件注入内容
- 作用域插槽:子 → 父,子组件暴露数据,父组件决定渲染方式
3. 插槽内容在哪个作用域编译?
解析:在父组件作用域编译,只能访问父组件的数据和方法。需要子组件数据时使用作用域插槽。
4. Vue 3 中如何写具名作用域插槽?
vue
<Child v-slot:item="{ row }">
{{ row.name }}
</Child>
<!-- 或 -->
<Child #item="{ row }">
{{ row.name }}
</Child>
5. 为什么 v-slot 不能直接用在 div 等元素上?
解析:v-slot 是编译时指令,用于标记插槽内容边界。Vue 3 要求它用在 <template> 上,以便编译器正确识别插槽边界;默认插槽的内容可以直接写在组件标签内。
总结
- 插槽:Vue 的内容分发机制,父组件向子组件注入内容
- 默认插槽 :
<slot>,父组件直接写内容 - 具名插槽 :
<slot name="xxx">,父组件用#xxx指定 - 作用域插槽 :子组件通过
<slot :prop="val">暴露数据,父组件自定义渲染 - 编译作用域:插槽内容在父组件作用域编译
- Vue 3 :统一
v-slot/#,移除 slot-scope