Vue 插槽和作用域插槽是什么?适合解决什么问题?
简化版
插槽让父组件把内容传给子组件指定位置,作用域插槽让子组件把数据暴露给父组件决定怎么渲染。它们常用于布局组件、列表组件、表格组件、弹窗组件,让组件既封装结构,又保留内容定制能力。
详细版
默认插槽:
<Card>
<p>这里是卡片内容</p>
</Card>
具名插槽:
<Card>
<template #header>标题</template>
<template #default>正文</template>
</Card>
作用域插槽:
<UserList v-slot="{ user }">
<span>{{ user.name }}</span>
</UserList>
核心区别是:普通插槽传内容,作用域插槽传“渲染权 + 子组件数据”。
完整版教学
一、插槽解决组件内容可定制的问题
组件封装常遇到一个矛盾:子组件想复用结构,但具体内容又要由父组件决定。 如果全部写死,组件不够灵活;如果全部交给父组件,封装价值又变低。 插槽就是这个折中点。
<!-- 父组件 -->
<BaseCard>
<p>订单金额:100 元</p>
</BaseCard>
<!-- BaseCard -->
<div class="card">
<slot />
</div>
这里卡片边框、间距、阴影由 BaseCard 管,内部内容由父组件提供。
这就是“结构由子组件封装,内容由父组件填充”。
二、默认插槽适合单内容区域
默认插槽只有一个主要内容出口。 它适合按钮、卡片、弹窗正文、布局容器等简单场景。 如果父组件没有传内容,子组件还可以提供 fallback 内容。
<button class="btn">
<slot>提交</slot>
</button>
当父组件写 <BaseButton /> 时显示“提交”。
当父组件写 <BaseButton>保存</BaseButton> 时显示“保存”。
这个设计让组件有默认行为,也允许调用方覆盖。
三、具名插槽适合多区域布局
当组件有多个可定制区域时,需要具名插槽。 例如弹窗通常有 header、body、footer。 具名插槽能让父组件准确填到指定区域。
<BaseModal>
<template #header>删除确认</template>
<template #default>确定删除这 3 条记录吗?</template>
<template #footer>
<button>取消</button>
<button>确定</button>
</template>
</BaseModal>
| 插槽类型 | 适合场景 | 特点 |
|---|---|---|
| 默认插槽 | 单内容区域 | 写法最简单 |
| 具名插槽 | 多区域组件 | 内容位置明确 |
| 作用域插槽 | 子传数据给父渲染 | 渲染更灵活 |
有 3 个区域时,用具名插槽比传 3 个字符串 prop 更灵活,因为 footer 里可以放按钮、图标、权限判断等任意模板。
四、作用域插槽让子组件提供数据,父组件决定视图
作用域插槽的核心是:数据在子组件,模板在父组件。 它特别适合列表、表格、选择器这种“逻辑统一但每行展示不同”的组件。 子组件负责遍历和状态,父组件负责具体怎么显示每一项。
<!-- 子组件 UserList.vue -->
<template>
<ul>
<li v-for="user in users" :key="user.id">
<slot :user="user" :index="user.id" />
</li>
</ul>
</template>
<!-- 父组件 -->
<UserList v-slot="{ user }">
<strong>{{ user.name }}</strong>
<span>{{ user.role }}</span>
</UserList>
如果有 20 个页面都需要用户列表,但每个页面展示字段不同,作用域插槽能避免写 20 个列表组件。 它把“列表逻辑”和“行模板”拆开了。
五、插槽内容的作用域属于父组件
一个重要原则:插槽模板写在父组件里,所以它能访问父组件作用域。 默认情况下,它不能直接访问子组件内部变量。 如果需要子组件数据,必须通过作用域插槽显式传出。
<Child>
{{ parentTitle }}
</Child>
上面的 parentTitle 来自父组件。
如果想用子组件里的 item,子组件必须写 <slot :item="item" />。
这能避免作用域混乱,让数据来源更明确。
六、插槽是组件库设计的高频能力
表格组件、表单组件、弹窗组件、布局组件大量使用插槽。 比如表格组件可以提供列配置,也可以用作用域插槽定制单元格。 这样既保证表格的分页、排序、加载逻辑统一,又让业务能自定义渲染。
<DataTable :rows="rows">
<template #cell-status="{ row }">
<StatusTag :value="row.status" />
</template>
</DataTable>
记忆钩子:普通插槽是父给子“内容”,作用域插槽是子给父“数据”,最终由父决定怎么画。
组件库里的插槽越多,不代表设计越好。 太多插槽会让 API 复杂,应该围绕稳定扩展点设计。
七、常见误区与追问
- 误区:插槽只是把 HTML 传给子组件。 插槽本质是组件内容分发机制,可以传组件、表达式、条件渲染和事件绑定。
- 误区:插槽内容能随便访问子组件变量。 插槽模板属于父组件作用域,访问子组件数据需要作用域插槽显式传递。
- 误区:作用域插槽只适合列表。 列表和表格最常见,但选择器、树组件、弹窗 footer、表单项也常用。
- 追问:具名插槽为什么比多个 prop 灵活? prop 适合数据,插槽适合模板内容,按钮、组件、权限判断都能放进插槽。
- 追问:作用域插槽会不会破坏封装? 不会,它只暴露子组件愿意暴露的数据,结构和状态逻辑仍由子组件控制。
- 追问:组件库设计插槽要注意什么? 插槽名要稳定,暴露数据要克制,避免让业务依赖内部实现细节。
八、加强记忆
插槽这题记成“子组件留坑,父组件填内容”。默认插槽填一个坑,具名插槽填多个坑,作用域插槽则是子组件把数据递出来,让父组件决定怎么渲染。回答时用弹窗和表格两个例子,能很好体现组件复用价值。