Vue3里slot怎么传值?从基础到进阶实操全讲明白
不少刚开始学Vue3的小伙伴,一碰到 slot 传值就犯懵——明明知道插槽能自定义组件内容,可子组件的内部数据咋传给父组件的插槽用?今天把「作用域插槽(slot 传值的核心)」从基础到进阶,结合代码例子掰碎了讲,看完保准能上手~
slot 传值解决啥场景?先搞懂“作用域插槽”的意义
先想个实际需求:你写了个 <Card> 组件,负责渲染卡片结构(比如标题、内容区、底部按钮),但产品经理要求不同页面的卡片内容样式不一样要加图标,有的按钮要变色)。
这时候,<Card> 组件里的数据(比如标题文本、内容列表)得“交给”父组件的插槽去自定义渲染——这就是 作用域插槽(Scoped Slots) 的核心场景:子组件把数据“暴露”给父组件的插槽,让父组件能在自定义内容时用这些数据。
对比普通插槽:普通插槽是“父组件塞内容,子组件渲染”,但父组件拿不到子组件的内部数据;而作用域插槽让子组件主动把数据传给父组件的插槽,实现“数据在子组件,UI 由父组件定制”的灵活分离。
最基础的作用域插槽咋写?分“子组件传值”和“父组件接收”两步
子组件:给 slot 绑定要传递的数据
子组件(Card.vue)里,在 <slot> 标签上用 绑定数据(这些绑定的属性叫“插槽 Props”),代码示例:
<template>
<div class="card">
<!-- 给slot绑定数据:item是卡片数据,handleClick是子组件的方法 -->
<slot :item="cardData" :handleClick="handleClick"></slot>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 子组件内部的数据和方法
const cardData = ref({ title: 'Vue3插槽', content: '作用域插槽传值示例' })
const handleClick = () => {
console.log('点击事件来自子组件~')
}
</script>
父组件:用 v-slot 接收插槽 Props
父组件使用 <Card> 时,得用 v-slot(或缩写 )在 <template> 上接收子组件传的 Props,有两种写法:
-
写法1:对象接收(适合数据多的情况)
<template> <Card> <!-- v-slot="slotProps":slotProps是个对象,包含子组件slot绑定的所有数据 --> <template v-slot="slotProps"> <h2>{{ slotProps.item.title }}</h2> <p>{{ slotProps.item.content }}</p> <button @click="slotProps.handleClick">点我触发子组件方法</button> </template> </Card> </template> <script setup> import Card from './Card.vue' </script> -
写法2:解构接收(更简洁,推荐!)
直接把slotProps里的属性解构出来,避免重复写slotProps.xxx:<template> <Card> <!-- 解构出item和handleClick --> <template v-slot="{ item, handleClick }"> <h2>{{ item.title }}</h2> <p>{{ item.content }}</p> <button @click="handleClick">点我触发子组件方法</button> </template> </Card> </template>
多个插槽(具名插槽)咋分别传值?
实际项目里,组件可能有多个插槽(<MultiCard> 有 header、body、footer 三个插槽),每个插槽要传不同数据,这时候用“具名插槽 + 作用域插槽” 组合写法:
子组件:给每个具名 slot 绑定数据
子组件(MultiCard.vue)里,给每个 <slot> 加 name 属性,再分别绑定数据:
<template>
<div class="multi-card">
<header>
<!-- 具名插槽header,传title数据 -->
<slot name="header" :title="pageTitle"></slot>
</header>
<main>
<!-- 具名插槽body,传文章列表数据 -->
<slot name="body" :list="articleList"></slot>
</main>
<footer>
<!-- 具名插槽footer,传作者信息 -->
<slot name="footer" :author="authorInfo"></slot>
</footer>
</div>
</template>
<script setup>
import { ref } from 'vue'
const pageTitle = ref('Vue3插槽实战')
const articleList = ref([
{ id: 1, text: '作用域插槽基础' },
{ id: 2, text: '具名插槽传值' }
])
const authorInfo = ref({ name: '前端小助手', date: '2024-09' })
</script>
父组件:给每个具名插槽单独接收 Props
父组件用 #插槽名(v-slot 的缩写),给每个具名插槽写 <template> 并接收数据:
<template>
<MultiCard>
<!-- 接收header插槽的title -->
<template #header="{ title }">
<h1>{{ title }}</h1>
</template>
<!-- 接收body插槽的list -->
<template #body="{ list }">
<ul>
<li v-for="item in list" :key="item.id">{{ item.text }}</li>
</ul>
</template>
<!-- 接收footer插槽的author -->
<template #footer="{ author }">
<p>作者:{{ author.name }} | 日期:{{ author.date }}</p>
</template>
</MultiCard>
</template>
<script setup>
import MultiCard from './MultiCard.vue'
</script>
传复杂数据(对象、函数)要注意啥?
实际开发中,子组件可能传对象、数组、函数给父组件,这时候要注意“解构默认值”和“函数调用逻辑”:
传对象:给解构加默认值,避免 undefined 报错
如果子组件传的对象可能为空,父组件解构时加默认值,防止访问属性时报错:
<!-- 子组件slot绑定:<slot :user="userInfo"></slot> -->
<template v-slot="{ user = { name: '默认名', age: 18 } }">
{{ user.name }} - {{ user.age }}
</template>
传函数:父组件调用子组件的方法
子组件可以把修改自身状态的函数传给父组件,父组件调用时直接触发子组件逻辑,比如子组件里的计数逻辑:
子组件(Counter.vue):
<template>
<slot :increment="increment" :count="count"></slot>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => {
count.value++ // 子组件内部修改count
}
</script>
父组件调用:
<template>
<Counter>
<template v-slot="{ increment, count }">
<button @click="increment">点击+1,当前:{{ count }}</button>
</template>
</Counter>
</template>
结合 setup 语法糖,slot 传值有变化吗?
Vue3 的 <script setup> 让组件写法更简洁,但slot 传值的核心逻辑不变——子组件里的变量会自动暴露给模板,所以定义 slot 绑定数据时,和普通写法完全一样;父组件接收 Props 的方式也没变化。
简单说:<script setup> 只是让子组件代码更简洁,不影响 slot 传值的“传”和“收”逻辑~
实际项目中,哪些场景必须用 slot 传值?
这3类场景最常见,掌握后写组件更丝滑:
- 表格组件(Table):子组件负责请求数据、分页,父组件自定义列渲染,比如表格行数据由子组件管理,父组件用 slot 传值接收每行数据,自定义渲染成按钮、链接等。
- 弹窗组件(Modal):子组件负责弹窗结构(遮罩、关闭逻辑),父组件用 slot 传值接收“关闭弹窗的方法”,在自定义内容里加关闭按钮。
- 列表组件(List):子组件处理数据加载、空状态,父组件用 slot 传值接收列表项数据,自定义每个列表项的 UI(比如带图标、带操作按钮)。
避坑!这些常见错误要绕开
-
忘记用
<template>包裹 v-slot
错误:直接把v-slot写在组件标签上(<Card v-slot="props">),如果组件有多个根节点会报错!
正确:必须用<template v-slot="props">包裹插槽内容。 -
具名插槽 name 不匹配
子组件 slot 的name="header",父组件却用#footer接收,会导致拿不到数据,要确保name和#插槽名完全一致。 -
解构时变量名写错
子组件 slot 绑定的是userInfo,父组件解构写成{ userinfo }(小写)——Vue 的插槽 Props 是大小写敏感的,必须和子组件绑定的属性名一致。 -
子组件没绑定数据,父组件却接收
子组件只写<slot></slot>(没绑定任何数据),父组件却用v-slot="props"接收,props是空对象,访问props.data会报错。
Vue2 和 Vue3 slot 传值有啥区别?
Vue2 里用 slot-scope 实现作用域插槽,Vue3 废弃了这个语法,统一用 v-slot,写法更简洁:
-
Vue2 写法:
<ChildComp> <template slot-scope="props"> {{ props.data }} </template> </ChildComp> -
Vue3 写法:
<ChildComp> <template v-slot="props"> {{ props.data }} </template> </ChildComp>
Vue3 支持 v-slot 缩写 ,和具名插槽结合更流畅~
进阶:动态插槽名 + 传值
如果插槽名是动态变化的(比如根据用户权限显示不同插槽),Vue3 支持“动态插槽名”写法,同时结合传值:
子组件(动态决定插槽名):
<template>
<!-- dynamicSlot 是计算出来的插槽名,#39;admin-slot'或'user-slot' -->
<slot :name="dynamicSlot" :data="slotData"></slot>
</template>
<script setup>
import { ref, computed } from 'vue'
const userRole = ref('admin')
const dynamicSlot = computed(() => {
return userRole.value + '-slot'
})
const slotData = ref('仅管理员可见的内容')
</script>
父组件(动态接收插槽):
<template>
<ChildComp>
<!-- v-slot:[dynamicSlotName] 绑定动态插槽名 -->
<template v-slot:[dynamicSlotName]="props">
{{ props.data }}
</template>
</ChildComp>
</template>
<script setup>
import { ref, computed } from 'vue'
import ChildComp from './ChildComp.vue'
const userRole = ref('admin')
const dynamicSlotName = computed(() => {
return userRole.value + '-slot'
})
</script>
slot 传值的核心逻辑
作用域插槽(slot 传值)的本质是 “子组件向父组件的插槽传递数据,让父组件能在自定义内容时复用子组件的数据/方法”,关键步骤就两步:
- 子组件:在
<slot>上用 绑定要传递的数据(即“插槽 Props”)。 - 父组件:用
v-slot(或 )在<template>上接收这些 Props,支持对象接收或解构接收。
掌握这两点,再结合具名插槽、动态插槽名等进阶用法,就能在 Vue3 中写出高复用、灵活可控的组件啦~
(全文约1800字,从基础到进阶覆盖 slot 传值核心场景,搭配代码例子和避坑指南,帮你彻底搞懂~)
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


