banner
NEWS LETTER

Vue 2 拒绝硬编码:3 分钟彻底搞懂插槽

Scroll down

第一部分:为什么要用插槽?

你是不是也遇到过这种情况:封装了一个通用的弹窗组件 (<MyDialog>)。弹窗的标题你用 props 传,内容你也用 props 传一段简单的文字或 HTML 字符串进来?这种方式只能传简单的文本。如果弹窗的内容里需要有复杂的按钮、点击事件、甚至另一张表格怎么办?用字符串传进来,Vue 根本无法解析和管理其中的组件和事件,你的组件瞬间就废了。

这就是硬编码的弊端——组件的内容被锁死了!

Vue 的插槽(Slots)就是为了解决“组件内容自定义”的问题。

它就像电脑上的 USB 接口 。鼠标、键盘、U盘(你的自定义内容)随便你插什么,只要接口(插槽)对得上就能用。它允许你在使用组件时,将 HTML 结构和逻辑 注入到子组件的特定位置。


第二部分:基础铺垫

我们先用最快的速度回顾两个简单插槽,为理解重头戏做准备。

1. 匿名插槽 (Default Slot)

这是最简单、最常见的插槽,就像一道填空题

  • 子组件:在需要被替换的地方挖个坑:<slot></slot>
  • 父组件:把内容放在子组件标签对之间,填个土:
1
2
3
<MyComponent>
<div>我是要注入到 <slot> 里的内容</div>
</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 组件,用来循环展示数据。

  1. 数据在哪里? 在子组件 (List 组件) 里(比如通过接口请求回来的 list 数组)。
  2. 样式和逻辑在哪里? 在父组件里(因为父组件要根据不同的场景,展示不同的列表样式)。

完蛋了!父组件想要决定每一条数据如何显示,但是数据在儿子手里,老爸够不着!

  • 情况A:父组件想把某条文字变成红色
  • 情况B:父组件想在文字前面加个删除按钮

结论: 老爸想玩数据,儿子必须把自己的数据‘递上来’。这就是作用域插槽。它允许子组件在定义插槽时,将自己的数据 “暴露” 给父组件。

2. 经典比喻:厨师与顾客

角色 行为 作用域插槽对应
子组件(厨师) 我负责获取食材(数据),并进行循环。但我不知道你想吃蒸的还是炸的。所以我把食材准备好,放在盘子里递给你。 <slot> 上绑定数据(v-bind:row="item"),将数据暴露给父组件。
父组件(顾客) 我拿到你递出来的食材,我自己决定怎么烹饪(渲染成什么 HTML)。 通过 v-slot="slotProps" 接收数据,并在模板内使用这些数据进行渲染

核心概念:数据从子组件(下)流向父组件(上),但渲染结构由父组件决定。

3. 代码实战(Vue 2 写法)

子组件 (ChildList.vue):负责干活(循环数据并暴露)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<ul>
<li v-for="(item, index) in list" :key="item.id">

<slot :row="item" :index="index">
{{ item.text }}
</slot>

</li>
</ul>
</template>

<script>
export default {
data() {
return {
list: [
{ id: 1, text: 'Vue 2', level: 'Easy' },
{ id: 2, text: 'Webpack', level: 'Hard' },
{ id: 3, text: 'Node', level: 'Normal' }
]
}
}
}
</script>

父组件 (App.vue):负责指挥(接收数据并决定样式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div>
<h3>案例:作用域插槽演示</h3>

<child-list>
<template v-slot:default="slotProps">

<span style="color: blue; font-weight: bold;">
{{ slotProps.index + 1 }} - {{ slotProps.row.text }}
</span>

<span v-if="slotProps.row.level === 'Hard'" style="color: red;">
(难哭你) 😭
</span>

</template>
</child-list>
</div>
</template>

<script>
import ChildList from './ChildList.vue'

export default {
components: {
ChildList
}
}
</script>

第四部分:小白最容易晕的地方(新手防坑指南)

1. 对象解构(高手必备)

v-slot:default="slotProps" 拿到的 slotProps 是一个包裹对象。

  • 子组件写了 :row="item":index="index",那么 slotProps 就是 { row: item, index: index }
  • 所以你要取数据就得写 slotProps.row

高手写法:可以直接使用 ES6 的对象解构,代码更简洁:

1
2
3
<template v-slot:default="{ row, index }">
<span style="color: blue;">{{ row.text }}</span>
</template>

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
2
3
<template v-slot:default="{ row }">
<button @click="handleClick(row)">点击我</button>
</template>

答案: 在插槽内部,所有指令和数据绑定都属于父组件的作用域

  • handleClick 方法是父组件的方法。
  • row 这个数据是通过 slotProps 传进来的子组件数据

记住:插槽的内容是在父组件中编译和定义的,只有通过 <slot> 标签传出来的属性才是子组件的数据。


第五部分:总结

插槽类型 父组件提供 子组件提供 核心作用
普通插槽 HTML 结构 + 数据 解决结构自定义
作用域插槽 HTML 结构 + 逻辑 数据 解决结构自定义,且内容数据由子组件提供

希望通过这个 “厨师与顾客” 的比喻,你已经彻底掌握了这个让无数人头疼的作用域插槽!现在,去实战中应用吧,用它来构建更灵活、更强大的组件!

其他文章
目录导航 置顶
  1. 1. 第一部分:为什么要用插槽?
  2. 2. 第二部分:基础铺垫
    1. 2.1. 1. 匿名插槽 (Default Slot)
    2. 2.2. 2. 具名插槽 (Named Slots)
  3. 3. 第三部分:重头戏 —— 作用域插槽 (Scoped Slots)
    1. 3.1. 这里是小白的火葬场,请放慢速度
    2. 3.2. 1. 核心矛盾:数据在儿子手里,样式在老爸手里
    3. 3.3. 2. 经典比喻:厨师与顾客
    4. 3.4. 3. 代码实战(Vue 2 写法)
    5. 3.5. 子组件 (ChildList.vue):负责干活(循环数据并暴露)
    6. 3.6. 父组件 (App.vue):负责指挥(接收数据并决定样式)
  4. 4. 第四部分:小白最容易晕的地方(新手防坑指南)
    1. 4.1. 1. 对象解构(高手必备)
    2. 4.2. 2. 新旧语法(维护老项目会看到)
    3. 4.3. 3. this 的指向问题
  5. 5. 第五部分:总结