Vue3里slot嵌套该怎么用?从基础到进阶全搞懂
slot嵌套的核心逻辑是啥?
你可以把 slot 理解成“组件里的占位符”——父组件往这个占位符里塞内容,子组件负责把内容显示在指定位置,那 slot嵌套 呢?占位符里再放占位符”,比如父组件给子组件A传内容,子组件A的某个区域又要用子组件B来渲染,而子组件B也需要父组件来控制内容,这时候子组件A的 slot 里就得再放子组件B的 slot,形成嵌套关系。
简单说,slot嵌套是为了让父组件能“穿透”多层子组件,精准控制最深层的自定义内容,比如做一个带头部、内容、底部的弹窗组件,头部里还要放自定义的标题和操作按钮,这时候头部组件(子组件)的 slot 里得再放按钮的 slot(孙子级 slot),父组件既能控制弹窗整体结构,又能控制头部里的按钮。
基础玩法:父→子的单层级slot嵌套咋写?
先看最基础的“父组件→子组件→孙子组件”嵌套结构,用代码直观理解:
子组件(Child.vue)
<template>
<div class="child-box">
<h2>我是子组件</h2>
<!-- 这里的<slot>是父组件的“入口”,但slot里又用了GrandChild组件 -->
<slot>
<!-- 如果父组件没传内容,就显示fallback内容(GrandChild) -->
<GrandChild>
<!-- GrandChild里的<slot name="grand-slot">是孙子级占位符 -->
<slot name="grand-slot"></slot>
</GrandChild>
</slot>
</div>
</template>
<script setup>
import GrandChild from './GrandChild.vue'
</script>
孙子组件(GrandChild.vue)
<template>
<div class="grand-box">
<h3>我是孙子组件</h3>
<!-- 这里的<slot>是给父组件控制内容的入口 -->
<slot></slot>
</div>
</template>
父组件(Parent.vue)
<template>
<Child>
<!-- 填充子组件的默认slot -->
<template #default>
<p>父组件给子组件的内容</p>
<!-- 填充孙子组件的grand-slot -->
<template #grand-slot>
<button>父组件控制的孙子组件按钮</button>
</template>
</template>
</Child>
</template>
逻辑拆解:
- 父组件用
<Child>时,通过#default给子组件传内容; - 子组件的
<slot>里嵌套了<GrandChild>,而<GrandChild>里又有自己的<slot name="grand-slot">; - 父组件要控制孙子组件的内容,就得在
#default里再用#grand-slot来填充——这就是 单层级嵌套(父→子→孙)的核心逻辑。
进阶:多层嵌套(父→子→孙→曾孙)咋玩数据传递?
当嵌套层级变多(父→子→孙→曾孙”),数据怎么从最外层传到最内层?这时候 作用域插槽(带数据的 slot)必须安排上。
举个“用户信息多层传递”的例子:
- 子组件有用户数据
user,要传给孙子组件; - 孙子组件再把数据传给曾孙组件;
- 父组件不仅要控制曾孙组件的内容,还要拿到最外层的用户数据。
子组件(Child.vue)
<template>
<div class="child">
<h3>子组件</h3>
<!-- 作用域插槽:把user数据传给父组件 -->
<slot :user="user"></slot>
</div>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({ name: '小明', age: 18 })
</script>
孙子组件(GrandChild.vue)
<template>
<div class="grand">
<h4>孙子组件</h4>
<!-- 把收到的user再传给曾孙(通过v-bind透传) -->
<slot v-bind="props.user"></slot>
</div>
</template>
<script setup>
const props = defineProps(['user'])
</script>
曾孙组件(GreatGrand.vue)
<template>
<div class="great-grand">
<h5>曾孙组件</h5>
<!-- 显示用户数据 -->
<p>姓名:{{ user.name }}, 年龄:{{ user.age }}</p>
<!-- 曾孙的slot,父组件可以控制这里的内容 -->
<slot></slot>
</div>
</template>
<script setup>
defineProps(['user'])
</script>
父组件(Parent.vue)
<template>
<Child v-slot="childScope">
<!-- childScope里有子组件传的user -->
<GrandChild v-slot="grandScope">
<!-- grandScope里有孙子组件传的user(和childScope.user是同一个) -->
<GreatGrand :user="grandScope.user">
<button>父组件控制的曾孙按钮</button>
</GreatGrand>
</GrandChild>
</Child>
</template>
关键点:
- 每一层组件都用 作用域插槽(
<slot :数据="变量">)把数据传出去; - 父组件用
v-slot="作用域变量"接收上一层的数据,再传给下一层; - 这样不管嵌套多少层,数据都能从外层“穿透”到内层,父组件也能精准控制每一层的内容。
作用域插槽和slot嵌套结合时,怎么避免作用域混乱?
多层嵌套+作用域插槽最容易踩的坑是 “作用域变量重名”——比如子组件和孙子组件都传了个 user,父组件接收时分不清谁是谁。
解决方法很简单:给作用域变量“重命名”,用 ES6 的结构赋值就行。
看优化后的父组件代码:
<template>
<Child v-slot="{ user: childUser }">
<!-- 把childScope的user重命名为childUser -->
<GrandChild v-slot="{ user: grandUser }">
<!-- 把grandScope的user重命名为grandUser -->
<GreatGrand :user="grandUser">
<p>父组件里用子组件数据:{{ childUser.name }}</p>
<button>按钮</button>
</GreatGrand>
</GrandChild>
</Child>
</template>
这样一来,childUser 明确是子组件传的,grandUser 是孙子组件传的,再也不会搞混啦~
实际项目中,哪些场景必须用slot嵌套?
很多“需要多层自定义”的组件场景,都得靠 slot 嵌套实现,举几个真实开发中常见的例子:
组件库的“布局组件”
AntDesignVue 的 Layout 组件,它包含 Header、Sider、Content、Footer 这几个子组件,每个子组件内部又允许用户自定义内容(Header 里放 logo 和导航按钮),这时候:
Layout的<slot>里嵌套Header组件;Header组件的<slot>里再嵌套“logo 插槽”和“导航插槽”;- 父组件通过多层
v-slot,就能同时控制布局结构和每个区域的细节。
表格组件的“自定义列”
像 ElementPlus 的 Table 组件,TableColumn 可以自定义单元格内容(比如渲染按钮、图片),这时候:
Table的<slot>里嵌套TableColumn;TableColumn的<slot>里再嵌套“单元格内容插槽”;- 父组件用
#default控制列结构,用#cell控制单元格里的自定义内容,实现“一列一逻辑”。
弹窗组件的“多层自定义”
做一个带头部、内容、底部的 Modal 弹窗:
Modal的<slot>里嵌套ModalHeader、ModalContent、ModalFooter;ModalHeader的<slot>里再嵌套“标题插槽”和“关闭按钮插槽”;- 父组件既能控制弹窗整体显示(比如是否显示头部),又能控制头部里的标题和按钮,灵活性拉满。
slot嵌套时,动态插槽名怎么玩?
动态插槽名是指“slot 的名称由变量决定”,语法是 v-slot:[动态变量],在嵌套场景中,它能让你根据业务逻辑 动态决定渲染哪个 slot。
举个“根据用户权限显示不同内容”的例子:
子组件(Child.vue)
<template>
<div>
<slot name="admin"></slot> <!-- 管理员专属slot -->
<GrandChild>
<!-- 动态传slot名给孙子组件 -->
<slot :dynamicSlot="currentSlot"></slot>
</GrandChild>
<slot name="guest"></slot> <!-- 游客专属slot -->
</div>
</template>
<script setup>
import { ref } from 'vue'
const currentSlot = ref('user-content') // 根据权限动态变化
</script>
孙子组件(GrandChild.vue)
<template>
<div>
<!-- 接收动态slot名,渲染对应的内容 -->
<slot :name="props.dynamicSlot"></slot>
</div>
</template>
<script setup>
defineProps(['dynamicSlot'])
</script>
父组件(Parent.vue)
<template>
<Child>
<template #admin>
<p>管理员才能看的内容</p>
</template>
<!-- 动态绑定slot名,currentSlot变了,渲染的内容也变 -->
<template v-slot:[currentSlot]="scope">
<p>这里是{{ scope.dynamicSlot }}的内容</p>
</template>
<template #guest>
<p>游客请登录</p>
</template>
</Child>
</template>
<script setup>
import { ref } from 'vue'
const currentSlot = ref('user-content')
</script>
核心逻辑:
- 子组件用
dynamicSlot把“该渲染哪个 slot”的变量传给孙子组件; - 孙子组件用
name="props.dynamicSlot"动态渲染 slot; - 父组件用
v-slot:[currentSlot]动态填充内容; - 这样整个嵌套结构的内容渲染逻辑,能根据业务变量(比如用户权限、页面状态)灵活变化。
常见坑点:slot嵌套时数据找不到、slot不渲染咋解决?
新手玩 slot 嵌套,很容易遇到“数据丢了”“slot 没渲染”的问题,总结几个高频坑和解决方案:
坑:“数据作用域错误”,内层slot拿不到外层数据
比如孙子组件想显示子组件的 user,但没通过作用域插槽传递。
解决:确保每一层组件都用 v-bind 把数据传到下一层,父组件用 v-slot 接收数据后再传给内层组件。
坑:“插槽名拼写错误”,导致slot不渲染
比如子组件的 slot 叫 #header,父组件写成 #head,名字对不上就不渲染。
解决:检查所有 name 属性和 v-slot 的名称是否完全一致(包括大小写)。
坑:“fallback内容干扰”,父组件没传内容时,fallback里的嵌套slot自动生效
比如子组件的 <slot> 里有 fallback 内容(默认显示孙子组件),但父组件其实想自定义这部分。
解决:明确 fallback 的作用——它是“父组件没传内容时的兜底”,如果要完全自定义,父组件必须传对应 slot 的内容,覆盖 fallback。
坑:“动态插槽名绑定错误”,写成静态名导致不生效
比如想动态渲染 slot,却写成 <template #currentSlot>(静态名),而不是 <template v-slot:[currentSlot]>。
解决:动态插槽名必须用 v-slot:[变量] 的语法,不能用 #变量(后者是静态名)。
slot嵌套的核心价值是“多层自定义”
Vue3 的 slot 嵌套,本质是 让父组件能“穿透”N层子组件,精准控制每一层的自定义内容,无论是做组件库、业务组件还是复杂页面,只要涉及“父组件需要控制多层子组件的局部内容”,slot 嵌套+作用域插槽就是最优解。
记住这几个关键逻辑:
- 嵌套结构:父→子→孙→…,每一层用
<slot>做占位符; - 数据传递:用 作用域插槽(
数据="变量")把数据从外层传到内层; 控制:父组件用v-slot接收数据,再通过命名 slot(#name)或动态 slot(v-slot:[变量]; - 避坑技巧:重命名作用域变量、检查插槽名拼写、理解 fallback 逻辑。
把这些逻辑吃透,再复杂的多层嵌套场景也能轻松hold住~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


