在 Vue3 组件化开发中,Props 解决的是“父组件向子组件传递数据”的问题。但是在实际开发中,经常会遇到这样的需求:
如果仅靠 Props,就会导致组件设计越来越复杂。
因此 Vue 提供了一个非常重要的机制:
插槽(Slot)
插槽可以理解为:
组件预留的位置,由使用组件的人决定具体显示什么内容。
插槽是 Vue 组件复用能力的重要组成部分,也是企业级组件库设计的核心技术之一。
本文将系统讲解 Vue3 插槽的全部知识点。
一、什么是插槽
先看一个按钮组件。
普通写法
子组件:
<template>
<button class="btn">
按钮
</button>
</template>
使用:
<MyButton />
页面显示:
<button>按钮</button>
这种写法的问题是:
按钮内容被写死了。
无法实现:
登录
注册
提交
保存
删除
不同场景的复用。
使用插槽
子组件:
<template>
<button class="btn">
<slot></slot>
</button>
</template>
父组件:
<MyButton>
登录
</MyButton>
页面渲染:
<button>
登录
</button>
继续使用:
<MyButton>
保存
</MyButton>
页面渲染:
<button>
保存
</button>
这样按钮组件就具备了更强的扩展能力。
二、插槽的本质
插槽本质上是:
内容分发机制
子组件负责布局。
父组件负责内容。
例如:
<MyButton>
登录
</MyButton>
这里:
登录
属于插槽内容。
而:
<slot></slot>
属于插槽出口。
Vue 会把父组件传递的内容渲染到指定位置。
三、插槽可以传递什么内容
插槽不仅可以传递文本。
还可以传递:
HTML
<MyButton>
<span>登录</span>
</MyButton>
多个元素
<MyButton>
<span>登录</span>
<span>注册</span>
</MyButton>
组件
<MyButton>
<IconUser />
</MyButton>
混合内容
<MyButton>
<IconUser />
用户中心
</MyButton>
插槽支持任意合法模板内容。
四、默认插槽
没有指定名称的插槽称为:
默认插槽
Default Slot
例如:
<template>
<slot></slot>
</template>
这是最常见的插槽形式。
使用:
<MyComponent>
内容
</MyComponent>
对应:
<slot></slot>
五、插槽的作用域
很多人刚学插槽时容易产生误解。
例如:
父组件:
<script setup>
const message = "Hello"
</script>
<MyButton>
{{ message }}
</MyButton>
可以正常显示:
Hello
因为插槽内容定义在父组件中。
但是:
子组件:
<script setup>
const title = "Vue3"
</script>
<slot></slot>
父组件:
<MyButton>
{{ title }}
</MyButton>
会报错。
因为:
插槽内容只能访问父组件作用域
无法访问子组件作用域
插槽遵循 JavaScript 的词法作用域规则。
六、默认内容
很多时候希望:
如果用户没有传递插槽内容。
则显示默认内容。
例如:
<template>
<button>
<slot>
提交
</slot>
</button>
</template>
使用:
<MyButton />
显示:
提交
如果:
<MyButton>
保存
</MyButton>
则显示:
保存
外部内容会覆盖默认内容。
七、为什么需要具名插槽
假设开发一个页面布局组件:
Header
Main
Footer
如果只有一个插槽:
<slot></slot>
显然无法区分:
头部内容
主体内容
底部内容
因此需要:
具名插槽
Named Slot
八、具名插槽
子组件:
<template>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</template>
这里有:
header
default
footer
三个插槽。
父组件:
<BaseLayout>
<template #header>
页面头部
</template>
页面主体
<template #footer>
页面底部
</template>
</BaseLayout>
页面渲染:
页面头部
页面主体
页面底部
九、v-slot 指令
Vue 使用:
v-slot
指定插槽内容。
完整写法:
<template v-slot:header>
</template>
简写:
<template #header>
</template>
企业项目中几乎都使用简写。
十、默认插槽简写
显式写法:
<template #default>
</template>
例如:
<BaseLayout>
<template #default>
页面主体
</template>
</BaseLayout>
通常直接写:
<BaseLayout>
页面主体
</BaseLayout>
即可。
十一、条件插槽
有时希望:
传了插槽才渲染区域
没传则不渲染
例如卡片组件:
<div v-if="$slots.header">
<slot name="header"></slot>
</div>
如果父组件没有提供:
<template #header>
</template>
整个 Header 区域不会渲染。
这种写法在企业项目中非常常见。
十二、$slots 对象
Vue 会自动维护:
$slots
例如:
console.log($slots)
输出:
{
default: Function,
header: Function,
footer: Function
}
可以用于:
判断插槽是否存在
动态控制布局
十三、动态插槽名
Vue3 支持动态插槽。
子组件:
<slot :name="slotName"></slot>
父组件:
<template #[slotName]>
内容
</template>
适用于:
动态表格
动态布局
动态表单
等高级场景。
十四、作用域插槽
这是 Vue 插槽中最重要的知识点。
很多人第一次学习时都会困惑。
先看问题。
子组件:
<script setup>
const users = [
{ id:1,name:"Tom" },
{ id:2,name:"Jack" }
]
</script>
希望父组件决定:
每条数据怎么显示
怎么办?
十五、作用域插槽解决的问题
子组件:
<ul>
<li
v-for="user in users"
:key="user.id"
>
<slot
:user="user"
></slot>
</li>
</ul>
这里:
:user="user"
向插槽暴露数据。
父组件:
<UserList
v-slot="slotProps"
>
{{ slotProps.user.name }}
</UserList>
显示:
Tom
Jack
这就是作用域插槽。
本质:
子组件向父组件传递数据
父组件决定如何渲染
十六、解构作用域插槽
实际开发中常用解构。
例如:
<UserList
v-slot="{ user }"
>
{{ user.name }}
</UserList>
等价于:
slotProps.user
但更简洁。
十七、具名作用域插槽
可以结合具名插槽使用。
子组件:
<slot
name="header"
:title="title"
></slot>
父组件:
<template #header="{ title }">
{{ title }}
</template>
这样:
指定区域
+
传递数据
同时实现。
十八、企业级开发中的插槽应用
自定义表格列
<Table>
<template #action="{ row }">
<a-button>
编辑
</a-button>
</template>
</Table>
弹窗组件
<MyDialog>
<template #header>
删除确认
</template>
<template #default>
是否删除当前数据
</template>
<template #footer>
操作按钮
</template>
</MyDialog>
卡片组件
<Card>
<template #header>
用户信息
</template>
用户内容
<template #footer>
更多操作
</template>
</Card>
表单组件
<FormItem>
<template #label>
用户名称
</template>
</FormItem>
数据列表组件
<DataList
v-slot="{ item }"
>
{{ item.name }}
</DataList>
十九、插槽与 Props 的区别
简单理解:
Props 传数据
Slots 传界面
二十、企业级开发最佳实践
简单配置使用 Props
例如:
<Button
type="primary"
/>
复杂内容使用 Slot
例如:
<Card>
任意内容
</Card>
多区域布局使用具名插槽
例如:
Header
Body
Footer
必须使用:
Named Slot
数据渲染使用作用域插槽
例如:
Table
List
Tree
Menu
全部优先使用:
Scoped Slot
总结
插槽是 Vue3 组件设计的核心能力之一,其本质是内容分发机制。
Vue3 插槽体系主要包含以下内容: