第一部分:为什么要用插槽?
你是不是也遇到过这种情况:封装了一个通用的弹窗组件 (<MyDialog>)。弹窗的标题你用 props 传,内容你也用 props 传一段简单的文字或 HTML 字符串进来?这种方式只能传简单的文本。如果弹窗的内容里需要有复杂的按钮、点击事件、甚至另一张表格怎么办?用字符串传进来,Vue 根本无法解析和管理其中的组件和事件,你的组件瞬间就废了。
这就是硬编码的弊端——组件的内容被锁死了!
Vue 的插槽(Slots)就是为了解决“组件内容自定义”的问题。
它就像电脑上的 USB 接口 。鼠标、键盘、U盘(你的自定义内容)随便你插什么,只要接口(插槽)对得上就能用。它允许你在使用组件时,将 HTML 结构和逻辑 注入到子组件的特定位置。
第二部分:基础铺垫
我们先用最快的速度回顾两个简单插槽,为理解重头戏做准备。
1. 匿名插槽 (Default Slot)
这是最简单、最常见的插槽,就像一道填空题。
- 子组件:在需要被替换的地方挖个坑:
<slot></slot>。 - 父组件:把内容放在子组件标签对之间,填个土:
1 | <MyComponent> |
2. 具名插槽 (Named Slots)
当一个组件内部有多个自定义区域(如头部、身体、底部)时,就需要用具名插槽,实现对号入座。
- 比喻:票上写着“头等舱”,你就不能坐到“经济舱”的位置去。
| 区域 | 子组件(挖坑) | 父组件(填土) |
|---|---|---|
| 头部 | <slot name="header"></slot> |
<template v-slot:header> (简写 #header) |
| 主体 | <slot></slot> |
<template v-slot:default> (简写 #default) |
| 底部 | <slot name="footer"></slot> |
<template v-slot:footer> (简写 #footer) |
第三部分:重头戏 —— 作用域插槽 (Scoped Slots)
这里是小白的火葬场,请放慢速度
1. 核心矛盾:数据在儿子手里,样式在老爸手里
这是作用域插槽诞生的唯一理由:解决数据作用域的隔离问题。
假设你封装了一个强大的
List组件,用来循环展示数据。
- 数据在哪里? 在子组件 (
List组件) 里(比如通过接口请求回来的list数组)。- 样式和逻辑在哪里? 在父组件里(因为父组件要根据不同的场景,展示不同的列表样式)。
完蛋了!父组件想要决定每一条数据如何显示,但是数据在儿子手里,老爸够不着!
- 情况A:父组件想把某条文字变成红色。
- 情况B:父组件想在文字前面加个删除按钮。
结论: 老爸想玩数据,儿子必须把自己的数据‘递上来’。这就是作用域插槽。它允许子组件在定义插槽时,将自己的数据 “暴露” 给父组件。
2. 经典比喻:厨师与顾客
| 角色 | 行为 | 作用域插槽对应 |
|---|---|---|
| 子组件(厨师) | 我负责获取食材(数据),并进行循环。但我不知道你想吃蒸的还是炸的。所以我把食材准备好,放在盘子里递给你。 | 在 <slot> 上绑定数据(v-bind:row="item"),将数据暴露给父组件。 |
| 父组件(顾客) | 我拿到你递出来的食材,我自己决定怎么烹饪(渲染成什么 HTML)。 | 通过 v-slot="slotProps" 接收数据,并在模板内使用这些数据进行渲染。 |
核心概念:数据从子组件(下)流向父组件(上),但渲染结构由父组件决定。
3. 代码实战(Vue 2 写法)
子组件 (ChildList.vue):负责干活(循环数据并暴露)
1 | <template> |
父组件 (App.vue):负责指挥(接收数据并决定样式)
1 | <template> |
第四部分:小白最容易晕的地方(新手防坑指南)
1. 对象解构(高手必备)
v-slot:default="slotProps" 拿到的 slotProps 是一个包裹对象。
- 子组件写了
:row="item"和:index="index",那么slotProps就是{ row: item, index: index }。 - 所以你要取数据就得写
slotProps.row。
高手写法:可以直接使用 ES6 的对象解构,代码更简洁:
1 | <template v-slot:default="{ row, index }"> |
2. 新旧语法(维护老项目会看到)
在 Vue 2.6 版本之前,作用域插槽的写法是 slot-scope="scope",它写在普通 DOM 元素上。
- 旧语法 (2.6 前):
<child-list slot-scope="scope">...</child-list> - 新语法 (2.6 统一):
<template v-slot:default="slotProps">...</template>
建议: 主推新语法 v-slot,因为它统一了具名插槽和作用域插槽的写法,更清晰。
3. this 的指向问题
在插槽内部,如果你使用了父组件自己的数据或方法(例如一个 click 事件),它是属于谁的?
1 | <template v-slot:default="{ row }"> |
答案: 在插槽内部,所有指令和数据绑定都属于父组件的作用域。
handleClick方法是父组件的方法。row这个数据是通过slotProps传进来的子组件数据。
记住:插槽的内容是在父组件中编译和定义的,只有通过 <slot> 标签传出来的属性才是子组件的数据。
第五部分:总结
| 插槽类型 | 父组件提供 | 子组件提供 | 核心作用 |
|---|---|---|---|
| 普通插槽 | HTML 结构 + 数据 | 无 | 解决结构自定义 |
| 作用域插槽 | HTML 结构 + 逻辑 | 数据 | 解决结构自定义,且内容数据由子组件提供 |
希望通过这个 “厨师与顾客” 的比喻,你已经彻底掌握了这个让无数人头疼的作用域插槽!现在,去实战中应用吧,用它来构建更灵活、更强大的组件!