Vue3 的 slot 怎么用?从基础到实战全解析
新手学 Vue3 时,slot 常常让人摸不着头脑:到底啥是 slot?具名插槽、作用域插槽有啥区别?实际项目怎么用?今天用问答形式把 slot 讲透,从基础到实战一次性搞懂~
Vue3 里的 slot 是干啥的?
简单说,slot 是 Vue 里的“内容分发机制”——让父组件能控制子组件里的部分 UI 结构,同时子组件保留逻辑和样式的控制权。
举个实际场景:做一个弹窗组件 <Dialog>,弹窗的“标题”“底部按钮”想让父组件自定义,而“弹窗遮罩、关闭逻辑”由子组件自己实现,这时候用 slot 就很合适:子组件里留好 <slot> 占位,父组件往对应位置塞内容就行。
最基础的“匿名插槽”怎么写?
匿名插槽是最简单的用法,子组件里只放一个 <slot>,父组件往子组件标签里塞内容,就会替换这个 <slot>。
子组件(MyButton.vue):
<template>
<button class="btn">
<!-- 这里是插槽,父组件内容会替换这里 -->
<slot>默认按钮文字</slot>
</button>
</template>
<style scoped>
.btn { padding: 8px 16px; }
</style>
父组件中使用:
<template> <!-- 情况1:父组件传内容,替换默认值 --> <MyButton>提交</MyButton> <!-- 情况2:父组件没传内容,显示 slot 里的默认值“默认按钮文字” --> <MyButton></MyButton> </template>
关键点:匿名插槽只有一个,父组件没写内容时,子组件 <slot> 中间的“默认按钮文字”会显示(这叫 Fallback 内容)。
多个插槽咋区分?用“具名插槽”!
如果子组件需要多个可自定义的区域(比如页面布局有 header、main、footer),就得用具名插槽——给每个 <slot> 加 name 属性区分。
子组件(Layout.vue):
<template>
<div class="layout">
<!-- 头部插槽,name 是 header -->
<slot name="header"></slot>
<!-- 主体插槽,name 是 main -->
<slot name="main"></slot>
<!-- 底部插槽,name 是 footer -->
<slot name="footer"></slot>
</div>
</template>
父组件中使用:
父组件要用 <template> 配合 v-slot:(或缩写 )指定插槽名,把内容“精准投递”到对应位置。
<template>
<Layout>
<!-- 投递到 header 插槽 -->
<template #header>
<h1>页面标题</h1>
</template>
<!-- 投递到 main 插槽 -->
<template #main>
<p>这里是正文内容...</p>
</template>
<!-- 投递到 footer 插槽 -->
<template #footer>
<p>©2024 版权信息</p>
</template>
</Layout>
</template>
小细节:v-slot: 可以缩写成 ,但必须配合 <template> 使用(不能直接写在普通 DOM 元素上,除非是组件标签,但日常用 <template> 更规范)。
子组件的数据,父组件插槽想用?用“作用域插槽”!
场景:子组件里有循环数据(todo 列表),但每个 todo 项的 UI 想让父组件自定义,这时候子组件需要把内部数据传给父组件的插槽,这就是作用域插槽。
子组件(TodoList.vue):
子组件循环 todos 数组,把每个 todo 数据通过 todo="todo" 传给插槽。
<template>
<ul class="todo-list">
<li v-for="todo in todos" :key="todo.id">
<!-- 插槽绑定 todo 数据 -->
<slot :todo="todo"></slot>
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const todos = ref([
{ id: 1, title: '学习 Vue3', done: false },
{ id: 2, title: '写代码', done: true }
])
</script>
父组件中使用:
父组件用 #default="slotProps" 接收子组件传的内容,slotProps 是个对象,里面包含子组件绑定的 todo 数据,还能解构赋值简化写法~
<template>
<TodoList>
<!-- 方式1:接收整个 slotProps 对象 -->
<template #default="slotProps">
<span v-if="slotProps.todo.done">✔️</span>
{{ slotProps.todo.title }}
</template>
<!-- 方式2:解构赋值(更简洁) -->
<template #default="{ todo }">
<span v-if="todo.done">✔️</span>
{{ todo.title }}
</template>
</TodoList>
</template>
核心逻辑:子组件把数据“绑”在 <slot> 上(todo="todo"),父组件通过 #插槽名="变量" 接收,实现子传父数据 + 父组件自定义 UI的协作。
插槽名能动态变?试试“动态插槽名”!
场景:父组件想根据用户操作/配置,动态选择往子组件的哪个插槽里塞内容,Vue3 支持用方括号语法实现动态插槽名。
父组件示例:
<template>
<div>
<!-- 动态变量 slotName,比如可能是 'header' 或 'footer' -->
<template #[slotName]>
{{ 动态插槽的内容 }}
</template>
<!-- 也可以结合 v-bind 动态绑定 -->
<template v-slot:[dynamicSlotName]>
{{ 内容 }}
</template>
</div>
</template>
<script setup>
import { ref } from 'vue'
const slotName = ref('header') // 动态切换插槽名
const dynamicSlotName = ref('footer')
</script>
子组件只需要对应提供 name 为 header、footer 等的具名插槽即可,父组件动态选择要“投递”的目标。
插槽的“Fallback 内容”有啥用?
Fallback 备胎内容”——当父组件没给插槽传内容时,子组件 <slot> 中间的内容会显示。
比如子组件想给按钮默认文字,但允许父组件自定义:
<template>
<button>
<slot>默认按钮</slot> <!-- 父组件没传内容时,显示“默认按钮” -->
</button>
</template>
作用域插槽也能结合 Fallback!比如子组件循环列表,父组件没传插槽内容时,显示默认的列表项样式:
<template>
<li v-for="item in list" :key="item.id">
<slot :item="item">
<!-- Fallback 内容:默认显示 item.name -->
{{ item.name }}
</slot>
</li>
</template>
Vue3 和 Vue2 的 slot 有啥区别?
很多从 Vue2 转 Vue3 的同学会疑惑语法变化,3 个核心区别:
- 语法更统一:Vue2 里父组件用
<template slot="name">,子组件用<slot name="name">;Vue3 统一用v-slot:或 ,且必须写在<template>上。 - 作用域插槽更高效:Vue3 对作用域插槽的编译做了优化,性能更好,且语法更简洁(不用再写
slot-scope)。 - 组合式 API 支持:Vue3 可以在
setup里用useSlots()操作插槽(比如判断某个插槽是否存在),Vue2 没有这个 API。
插槽和组件通信有啥关系?
Vue 里组件通信方式很多(props、events、provide/inject),插槽是“内容级通信”——父组件给子组件传“带逻辑的模板”,子组件给父组件传“数据”(作用域插槽)。
举个经典场景:评论列表组件
- 子组件
<CommentList>负责拉取评论数据、处理加载状态。 - 父组件想自定义每条评论的 UI(比如加头像、点赞按钮)。
- 这时用作用域插槽:子组件把评论数据传给插槽,父组件接收后渲染自定义结构,完美解耦“数据逻辑”和“UI 展示”。
实战中插槽常见问题咋解决?
新手用插槽容易踩坑,分享 3 个高频问题和解法:
问题1:作用域插槽接收不到数据?
→ 检查两点:
- 子组件是否用 绑定数据(
<slot :todo="todo">,少了 就变成字符串传值了)。 - 父组件是否正确接收(
#default="{ todo }",别写错成slotProps.todo但没解构)。
问题2:具名插槽不生效?
→ 检查 name 匹配度:子组件 <slot name="header"> 和父组件 <template #header> 必须完全一致,大小写也要注意!
问题3:默认插槽和具名插槽混用混乱?
→ 父组件中,没写 <template #name> 的内容,会默认进入“匿名插槽”,如果子组件同时有匿名和具名插槽,要明确区分内容归属,避免结构混淆。
插槽在 UI 组件库中咋用?
主流 UI 库(Element Plus、Ant Design Vue)大量用插槽增强扩展性,举两个例子:
- 表格组件用作用域插槽自定义,
<el-table-column>的#default="scope",scope里包含行数据、索引等。 - 弹窗组件、底部按钮用具名插槽,
<el-dialog>的#title自定义标题,#footer自定义底部按钮。
学会插槽,你也能写出像 UI 库一样灵活的组件~
组合式 API 咋操作插槽?
Vue3 的 setup 中,用 useSlots() 可以获取当前组件的所有插槽,返回一个对象(键是插槽名,值是插槽内容的 VNode 数组)。
子组件示例(判断插槽是否存在):
<template>
<div>
<slot name="header"></slot>
<slot></slot>
</div>
</template>
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
// 检查是否有 header 插槽
if (slots.header) {
console.log('父组件提供了 header 插槽')
}
// 检查默认插槽是否有内容
if (slots.default) {
console.log('父组件给默认插槽传了内容')
}
</script>
这个 API 能让你在逻辑层动态处理插槽(比如插槽存在才渲染某部分),灵活度拉满~
slot 是 Vue 组件解耦的神器!
从基础匿名插槽,到具名、作用域、动态插槽,核心都是让父组件和子组件在“内容和逻辑”上各司其职:子组件管逻辑/样式,父组件管自定义结构,配合起来实现高度可复用的组件。
实际项目中,只要涉及“组件部分结构需要外部自定义”的场景,第一时间想到 slot 就对了~ 现在再回头看项目里的弹窗、表格、列表组件,是不是突然明白它们的插槽逻辑了?
帮你理清了 slot 用法,不妨动手写个带作用域插槽的列表组件试试,实践出真知~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



