Vue Slots 教程,从基础到进阶,搞懂组件内容分发
Vue Slots 到底是什么?能解决哪些开发痛点?
Vue 里的 Slots(插槽),是分发的机制,想象你做了个“卡片组件”,不同页面用它时,卡片里的标题、描述、按钮得自由定制——总不能每次改组件源码吧?Slots 就是用来解决“组件复用但内容要自定义”的问题。
Slots 让父组件能往子组件里“塞”任意内容(文字、HTML、其他组件都行),子组件只用 <slot> 标记“内容该插在哪”,这样一来,子组件管结构和逻辑,父组件管个性化内容,组件复用性和灵活性直接拉满。
举个实际痛点:电商项目里,商品卡片在首页、列表页、详情页布局逻辑一样,但展示信息不同(比如首页要促销标签,详情页要参数表格),没 Slots 的话,要么写多个相似组件,要么堆 v-if 判断——代码冗余又难维护,用 Slots 就清爽了:子组件留好插槽,父组件按需塞内容~
最基础的默认插槽(Default Slot)怎么用?
默认插槽适合子组件只有一个“自定义区域”的场景,步骤很简单:
子组件留插槽位置
在子组件模板里,用 <slot> 标记内容插入位置,比如做个卡片组件 MyCard:
<template>
<div class="card">
<!-- 插槽位置,父组件内容会替换到这 -->
<slot></slot>
</div>
</template>
<style scoped>
.card { border: 1px solid #eee; padding: 16px; border-radius: 4px; }
</style>
父组件往插槽塞内容
用 MyCard 时,直接在 <MyCard> 内部写要插入的内容(文字、HTML、其他组件都行):
<template>
<MyCard>
<h2>自定义卡片标题</h2>
<p>卡片描述信息...</p>
<button>按钮</button>
</MyCard>
</template>
<script setup>
import MyCard from './components/MyCard.vue'
</script>
(Fallback Content)是啥? 时,子组件能显示“默认内容”吗?给 <slot> 加默认内容:
<template>
<div class="card">
<slot>
<!-- 父组件没传内容时,显示这段 -->
<p>默认卡片内容~</p>
</slot>
</div>
</template>
``` 就替换默认内容,不传就显示后备内容,组件更“抗造”~
### 具名插槽(Named Slots)什么时候用?怎么写?
子组件有**多个“自定义区域”**(比如弹窗的头部、主体、底部),默认插槽不够用——得给插槽起名字,这就是**具名插槽**。
#### 啥时候用?
典型场景:弹窗(Modal)自定义标题/内容/按钮、布局组件(Layout)自定义侧边栏/主要内容、表格(Table)自定义表头/表体等。
#### 子组件怎么定义具名插槽?
给 `<slot>` 加 `name` 属性标记区域,比如做个弹窗组件 `Modal`:
```vue
<template>
<div class="modal-mask">
<div class="modal-content">
<!-- 头部插槽:name="header" -->
<slot name="header"></slot>
<!-- 主体插槽:name="body" -->
<slot name="body"></slot>
<!-- 底部插槽:name="footer" -->
<slot name="footer"></slot>
</div>
</div>
</template>
<style scoped>
.modal-mask { /* 遮罩样式 */ }
.modal-content { /* 弹窗内容样式 */ }
</style>
父组件怎么传内容?
用 <template> 配合 v-slot:插槽名(或缩写 #插槽名,比如父组件用 Modal:
<template>
<Modal>
<!-- 头部内容:对应 name="header" -->
<template #header>
<h2>弹窗标题</h2>
</template>
<!-- 主体内容:对应 name="body" -->
<template #body>
<p>弹窗详细内容...</p>
</template>
<!-- 底部内容:对应 name="footer" -->
<template #footer>
<button>取消</button>
<button>确定</button>
</template>
</Modal>
</template>
<script setup>
import Modal from './components/Modal.vue'
</script>
#header 是 v-slot:header 的语法糖(Vue 2.6+ 支持),写起来更简洁,子组件多个具名插槽,父组件要一一对应 name,没对应的不渲染~
作用域插槽(Scoped Slots)和普通插槽有啥区别?怎么传递数据?
普通插槽是“父组件往子组件塞内容”,但有时子组件想把内部数据传给父组件,让父组件用这些数据渲染——这就得用作用域插槽。
核心区别
- 普通插槽:数据流向「父 → 子」(父控内容,子给位置)。
- 作用域插槽:数据流向「子 → 父」(子传数据,父用数据渲染内容)。
子组件怎么传数据?
在子组件 <slot> 上通过属性绑定传数据,比如做个列表组件 UserList,让父组件自定义列表项渲染:
<template>
<ul class="user-list">
<li v-for="user in users" :key="user.id">
<!-- 给 slot 绑定 user 数据 -->
<slot :user="user"></slot>
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const users = ref([
{ id: 1, name: '小明', age: 18 },
{ id: 2, name: '小红', age: 20 }
])
</script>
<style scoped>
.user-list { list-style: none; padding: 0; }
.user-list li { /* 列表项样式 */ }
</style>
父组件怎么接收数据?
用 v-slot="插槽属性对象" 接收子组件传的属性,再在插槽内容里用,比如父组件想显示“姓名(年龄)”:
<template>
<UserList>
<!-- v-slot 接收 { user },解构后使用 -->
<template v-slot="{ user }">
<span>{{ user.name }}({{ user.age }}岁)</span>
</template>
</UserList>
</template>
<script setup>
import UserList from './components/UserList.vue'
</script>
子组件传多个数据(<slot :user="user" :index="index"></slot>),父组件可以这样收:
<template v-slot="{ user, index }">
<span>{{ index + 1 }}. {{ user.name }}</span>
</template>
作用域插槽的精髓是:子组件管数据,父组件管渲染,完美解耦~
插槽的后备内容(Fallback Content)有什么用?怎么设置?
是子组件给插槽准备的“兜底内容”——父组件没传内容就显示它,传了就替换。
有啥用?
让组件更“鲁棒”,比如做个按钮组件 MyButton,大部分场景按钮文字是“提交”,但某些页面要“保存”“删除”,给按钮插槽加后备内容,父组件不用每次传文字,减少重复代码。
怎么设置?
直接在子组件 <slot> 内部写默认内容。MyButton:
<template>
<button class="my-btn">
<slot>提交</slot> <!-- 父组件没传内容时,显示“提交” -->
</button>
</template>
<style scoped>
.my-btn { /* 按钮样式 */ }
</style>
父组件使用: <MyButton></MyButton> → 显示“提交”。 <MyButton>保存</MyButton> → 显示“保存”。
多个插槽嵌套时,作用域槽和具名槽结合怎么处理?
实际开发中,组件可能有多个区域(具名槽),某个区域还需子组件传数据(作用域槽),比如做个“复杂列表组件”,自定义头部、列表项、底部:
子组件 ComplexList 结构
<template>
<div class="complex-list">
<!-- 头部:具名槽 name="header" -->
<slot name="header"></slot>
<!-- 列表项:作用域槽,传每个 item 数据 -->
<ul>
<li v-for="item in list" :key="item.id">
<slot name="item" :item="item"></slot>
</li>
</ul>
<!-- 底部:具名槽 name="footer" -->
<slot name="footer"></slot>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, title: '文章1', author: '张三' },
{ id: 2, title: '文章2', author: '李四' }
])
</script>
<style scoped>
.complex-list { /* 组件样式 */ }
ul { /* 列表样式 */ }
li { /* 列表项样式 */ }
</style>
父组件怎么处理?
用 <template> 分别对应 name,作用域槽还要接收数据:
<template>
<ComplexList>
<!-- 头部:具名槽 header -->
<template #header>
<h2>热门文章列表</h2>
</template>
<!-- 列表项:具名槽 item + 作用域槽(接收 item 数据) -->
<template #item="{ item }">
<span>{{ item.title }} - 作者:{{ item.author }}</span>
</template>
<!-- 底部:具名槽 footer -->
<template #footer>
<p>以上是热门文章~</p>
</template>
</ComplexList>
</template>
<script setup>
import ComplexList from './components/ComplexList.vue'
</script>
关键点:具名槽用 #name 指定区域,作用域槽用 v-slot="{ 数据 }" 收子组件数据,对应上就不怕嵌套复杂~
Vue 3 对 Slots 有哪些新变化或优化?
Vue 3 对 Slots 语法和性能都做了优化,重点看这些:
组合式 API 中访问 Slots
在 <script setup> 里,用 useSlots() 访问当前组件的插槽信息(比如哪些插槽被使用、内容是什么),比如动态渲染插槽:
<template>
<div>
<slot name="custom"></slot>
</div>
</template>
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
console.log(slots) // 打印插槽信息
</script>
动态插槽名
Vue 3 支持动态绑定插槽名,用 v-slot:[变量] 语法,比如根据用户操作切换插槽:
<template>
<MyComponent>
<template v-slot:[dynamicSlot]>
动态切换的插槽内容
</template>
</MyComponent>
</template>
<script setup>
import { ref } from 'vue'
const dynamicSlot = ref('defaultSlot') // 动态改变变量切换插槽
</script>
性能优化
Vue 3 编译机制优化了插槽,减少不必要的重渲染,比如作用域插槽数据变化时,只有用到该数据的插槽会刷新,性能更优。
插槽语法更灵活
Vue 3 中,v-slot 可以直接用在组件标签上(不一定要包在 <template> 里),但建议用 <template> 更清晰:
<!-- 不推荐,但语法支持 -->
<MyComponent v-slot="props">
{{ props.data }}
</MyComponent>
<!-- 推荐:用 <template> 明确是插槽 -->
<MyComponent>
<template v-slot="props">
{{ props.data }}
</template>
</MyComponent>
实际项目里,Slots 能解决哪些典型场景?
Slots 是 Vue 组件化的“灵活性神器”,这些场景用它准没错:
场景 1:弹窗组件(Modal)多区域自定义 按钮区要自定义?用具名槽:
- 子组件
Modal用<slot name="header">放标题,<slot name="body">放内容,<slot name="footer">放按钮。 - 父组件传不同内容,弹窗结构/动画由子组件维护,内容由父组件自由发挥。
场景 2:列表组件(Table/List)行/列自定义
列表组件想让每一行渲染逻辑由父组件定?用作用域槽:
- 子组件
UserList循环数据,用<slot :user="user">把用户数据传给父组件。 - 父组件用
v-slot="{ user }"收数据,自定义渲染(比如显示头像+用户名)。
场景 3:布局组件(Layout)区域自定义
后台管理系统布局,侧边栏、主要内容、页脚要自定义?用具名槽:
- 子组件
Layout用<slot name="sidebar">放侧边栏,<slot name="main">放主要内容,<slot name="footer">放页脚。 - 父组件根据页面,传不同导航、内容、版权信息。
场景 4:通用卡片组件(Card)内容灵活化
卡片组件在不同页面需不同头部、内容、操作按钮?用默认槽+具名槽:
- 子组件
Card用<slot>放主体(默认槽),<slot name="header">放标题,<slot name="actions">放操作按钮。 - 父组件可只传主体(默认槽),也可传标题和按钮(具名槽),灵活度拉满。
场景 5:表单组件(FormItem)标签与错误提示自定义
表单组件想让标签文字、错误提示由父组件控?用具名槽:
- 子组件
FormItem用<slot name="label">放标签,<slot name="error">放错误提示,默认槽放输入框。 - 父组件根据表单项(用户名、密码),传不同标签和错误提示,组件逻辑(表单验证)由子组件维护。
这些场景核心逻辑:子组件管“固定结构/逻辑”,父组件管“可变内容/样式”,通过 Slots 解耦,组件复用性爆炸~
用 Slots 时常见的错误和坑怎么避?
Slots 好用但易踩坑,这几个问题要注意:
坑 1:作用域槽拿不到数据
表现:父组件 v-slot 接收数据时,props 空或数据不对。
原因:子组件 <slot> 没绑定数据,或父组件 v-slot 拼写错。
解决:
- 子组件检查:
<slot :data="data"></slot>有无绑定数据。 - 父组件检查:
v-slot="props"有无正确接收,或用解构v-slot="{ data }"。
坑 2:具名槽不渲染内容
表现:父组件传了具名槽内容,子组件对应位置空。
原因:子组件 <slot name="xxx"> 和父组件 <template #xxx> 的 xxx 不匹配;或父组件没包 <template>(Vue 2 必须用 <template>)。
解决:
- 确保子组件
slot的name和父组件#name完全一致(注意大小写)。 - 用
<template #name>包裹内容,避免歧义。
坑 3:插槽内容不更新
表现:父组件插槽内容依赖的响应式数据变了,插槽没刷新。
原因:数据不是响应式的(没用 ref/reactive),或插槽内容被缓存。
解决:
- 确保数据用
ref或reactive包裹,是响应式的。 - 作用域槽需检查子组件传递的数据是否响应式。
坑 4:后备内容不生效
表现:父组件没传内容,子组件没显示后备内容,反而空。
原因:父组件
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


