插槽 Slot
文章目录
- 前言
- 一、为什么需要插槽
- 1.1 问题
- 1.2 插槽的作用
- 二、默认插槽
- 2.1 基本用法
- 2.2 默认内容
- 三、具名插槽
- 3.1 定义
- 3.2 父组件使用
- 3.3 简写规则
- 四、作用域插槽
- 4.1 定义
- 4.2 父组件使用
- 4.3 具名 + 作用域插槽
- 五、应用场景
- 5.1 通用卡片组件
- 5.2 布局组件
- 5.3 表格自定义列
- 5.4 对话框组件
- 六、Vue 2 vs Vue 3 语法
- 6.1 变化对照
- 6.2 注意事项
- 七、面试聚焦
- 7.1 插槽内容的编译作用域
- 7.2 作用域插槽 vs 普通插槽
- 7.3 Vue 3 移除 slot-scope
- 八、易混淆点
- 九、思考与练习
- 总结
前言
插槽(Slot)是 Vue 的内容分发机制,让父组件可以向子组件模板中注入任意内容。本篇会讲清楚:
- 默认插槽、具名插槽、作用域插槽
v-slot/#简写- 插槽内容的编译作用域
- Vue 2 到 Vue 3 的语法变化
一、为什么需要插槽
1.1 问题
<!-- 子组件 Card 只能写死内容 --> <template> <div class="card"> <h3>固定标题</h3> <p>固定内容</p> </div> </template> <!-- 父组件无法自定义卡片内部内容 --> <Card />1.2 插槽的作用
插槽允许父组件向子组件注入自定义内容,子组件只负责布局和容器,内容由父组件决定。
<!-- 子组件 Card.vue --> <template> <div class="card"> <slot>默认内容</slot> </div> </template> <!-- 父组件 --> <Card> <h3>自定义标题</h3> <p>自定义内容</p> </Card>二、默认插槽
2.1 基本用法
<!-- 子组件 MyButton.vue --> <template> <button class="btn"> <slot>默认按钮文字</slot> </button> </template> <!-- 父组件 --> <MyButton>提交</MyButton> <!-- 渲染:<button class="btn">提交</button> --> <MyButton /> <!-- 渲染:<button class="btn">默认按钮文字</button> -->2.2 默认内容
<slot>标签内的内容是后备内容,父组件未传入内容时显示:
<slot> <span>暂无内容</span> </slot>三、具名插槽
3.1 定义
具名插槽通过name属性区分多个插槽,父组件使用v-slot:name或#name指定目标。
<!-- 子组件 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 父组件使用
<template> <Layout> <!-- 具名插槽:v-slot:header 或 #header --> <template #header> <h1>页面标题</h1> </template> <!-- 默认插槽 --> <p>主要内容区域</p> <template #footer> <p>© 2026 版权所有</p> </template> </Layout> </template>3.3 简写规则
<!-- 完整写法 --> <template v-slot:header>标题</template> <!-- 简写(推荐) --> <template #header>标题</template> <!-- 默认插槽简写 --> <template #default>内容</template> <!-- 或直接写内容,省略 template --> <Layout>内容</Layout>四、作用域插槽
4.1 定义
作用域插槽将子组件的数据通过 props 传递给父组件,让父组件控制渲染逻辑。
<!-- 子组件 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 父组件使用
<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 具名 + 作用域插槽
<!-- 子组件 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><!-- 父组件 --> <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 通用卡片组件
<!-- 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 布局组件
<!-- PageLayout.vue --> <template> <div class="page"> <aside><slot name="sidebar" /></aside> <main><slot /></main> </div> </template>5.3 表格自定义列
<!-- 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 对话框组件
<!-- 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 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 注意事项
<!-- ❌ v-slot 不能直接用在普通元素上 --> <div #header>标题</div> <!-- ✅ 必须用在 template 上 --> <template #header>标题</template> <!-- ✅ 默认插槽可以省略 template --> <MyComponent>直接写内容</MyComponent>七、面试聚焦
7.1 插槽内容的编译作用域
<!-- 插槽内容在父组件作用域中编译 --> <script setup> const parentMsg = '来自父组件' </script> <template> <Child> {{ parentMsg }} <!-- ✅ 可以访问父组件数据 --> <!-- {{ childMsg }} ❌ 无法访问子组件数据(除非作用域插槽) --> </Child> </template>7.2 作用域插槽 vs 普通插槽
// 普通插槽:父组件向子组件注入内容// 作用域插槽:子组件向父组件暴露数据,父组件决定如何渲染// 典型场景:表格/List 组件暴露 row 数据,父组件自定义列渲染7.3 Vue 3 移除 slot-scope
// 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 中如何写具名作用域插槽?
<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
