Vue3里slot怎么传参?默认、具名、作用域插槽传参门道全在这
Vue3 slot传参是解决啥问题的?
你写组件时,有没有遇到过“子组件有数据,但父组件要自定义这部分渲染”的情况?比如子组件做了列表请求,却想让父组件决定每个列表项长啥样;或者布局组件里,头部区域想让父组件放logo还是标题,这时候slot传参就派上用场了——让子组件把自己的数据“递”给父组件,父组件拿着这些数据,想咋渲染就咋渲染。
默认插槽咋传参?最基础的“数据传递”玩法
默认插槽就是没写name的<slot>,子组件想给父组件传数据,得用v-bind把数据绑到<slot>上;父组件用<template #default="接收对象">把数据接住。
举个栗子🌰:子组件Child.vue里有消息和列表,想让父组件自定义这部分展示:
<!-- 子组件 Child.vue -->
<template>
<div class="child-box">
<!-- 用v-bind把message和dataList绑到slot上 -->
<slot :msg="message" :list="dataList"></slot>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('这是子组件传的消息~')
const dataList = ref([1, 2, 3]) // 假设是列表数据
</script>
父组件用的时候,得用<template>包裹,通过#default接收参数(#default可以省略,直接写<template v-slot="slotProps">):
<!-- 父组件 -->
<Child>
<template #default="slotProps">
<!-- slotProps是个对象,包含msg和list -->
<p>{{ slotProps.msg }}</p>
<ul>
<li v-for="num in slotProps.list" :key="num">{{ num }}</li>
</ul>
</template>
</Child>
这里要注意:子组件传参是“主动给”,父组件是“被动接”,默认插槽的#default可以省略,写成<template v-slot="slotProps">也一样生效~
具名插槽传参:给不同“区域”精准递数据
如果子组件有多个插槽(比如头部、内容、底部),就得用具名插槽(给<slot>加name属性),传参逻辑和默认插槽一样,但父组件得对应name来接收数据。
比如做个页面布局组件Layout.vue,分header和content两个插槽,分别传标题、文章数据:
<!-- 子组件 Layout.vue -->
<template>
<header class="page-header">
<!-- name="header" 的插槽,传title和logo -->
<slot name="header" :title="pageTitle" :logo="logoUrl"></slot>
</header>
<main class="page-main">
<!-- name="content" 的插槽,传文章数据 -->
<slot name="content" :article="articleData"></slot>
</main>
</template>
<script setup>
import { ref } from 'vue'
const pageTitle = ref('我的个人博客')
const logoUrl = ref('/images/logo.png')
const articleData = ref({ 'Vue3插槽实战',
content: '这篇文章教你玩转slot传参...'
})
</script>
父组件用的时候,得用#header和#content分别对应,接收各自的参数:
<!-- 父组件 -->
<Layout>
<!-- 对应header插槽,接收headerProps -->
<template #header="headerProps">
<img :src="headerProps.logo" alt="站点logo">
<h1>{{ headerProps.title }}</h1>
</template>
<!-- 对应content插槽,接收contentProps -->
<template #content="contentProps">
<h2>{{ contentProps.article.title }}</h2>
<p>{{ contentProps.article.content }}</p>
</template>
</Layout>
关键点:具名插槽的name要和父组件的#name完全对应,不然父组件接不到数据,比如子组件是name="footer",父组件写成#foot就会凉凉~
作用域插槽?其实就是“带参的插槽”
Vue2里有个概念叫“作用域插槽”,Vue3里其实和“slot传参”是一回事——本质就是子组件把数据暴露给父组件的插槽,让父组件能基于这些数据渲染。
举个实际开发场景:表格组件Table.vue,子组件负责请求表格数据、处理分页,但每一列的显示(比如日期格式化、状态转文字)交给父组件自定义,这时候子组件得把每一行的数据传给父组件的插槽。
看例子👇:
<!-- 子组件 Table.vue -->
<template>
<table>
<thead>
<tr><th>姓名</th><th>年龄</th><th>操作</th></tr>
</thead>
<tbody>
<tr v-for="row in tableData" :key="row.id">
<!-- 每个td用插槽,把当前行数据row传出去 -->
<td><slot name="name" :row="row"></slot></td>
<td><slot name="age" :row="row"></slot></td>
<td><slot name="action" :row="row"></slot></td>
</tr>
</tbody>
</table>
</template>
<script setup>
import { ref } from 'vue'
const tableData = ref([
{ id: 1, name: '小明', age: 18 },
{ id: 2, name: '小红', age: 20 }
])
</script>
父组件用的时候,针对每个插槽传的row数据,自定义渲染:
<!-- 父组件 -->
<Table>
<template #name="{ row }">
<!-- 直接用row.name -->
{{ row.name }}
</template>
<template #age="{ row }">
<!-- 可以加逻辑,比如年龄+1 -->
{{ row.age + 1 }}
</template>
<template #action="{ row }">
<!-- 渲染按钮,绑定row.id -->
<button @click="handleEdit(row.id)">编辑</button>
</template>
</script>
<script setup>
const handleEdit = (id) => {
console.log('编辑用户:', id)
}
</script>
这种场景下,子组件只负责“提供数据和结构”,父组件负责“个性化渲染”,灵活性直接拉满!
进阶技巧:解构、默认值、TypeScript加持
传参后,父组件接收的slotProps是个对象,如果参数多,直接写slotProps.msg很麻烦,这时候可以解构赋值:
<Child>
<template #default="{ msg, list }">
<!-- 直接用msg和list,不用slotProps.xxx -->
<p>{{ msg }}</p>
<ul><li v-for="num in list">{{ num }}</li></ul>
</template>
</Child>
如果子组件没传某个参数(比如msg可能undefined),还能给默认值:
<template #default="{ msg = '默认消息', list = [] }">
<!-- 就算子组件没传msg,这里也不会报错 -->
<p>{{ msg }}</p>
</template>
如果用TypeScript开发,还能给插槽参数加类型,让IDE更智能:
<!-- 子组件 Child.vue,lang="ts" -->
<script setup lang="ts">
import { ref, defineSlots } from 'vue'
const message = ref('子组件消息')
const dataList = ref([1, 2, 3])
// 定义插槽参数类型
defineSlots({
default: (props: { msg: string; list: number[] }) => void
})
</script>
父组件接收时,TypeScript会自动提示msg是string,list是number[],减少拼写错误~
实际项目里,slot传参有哪些典型场景?
除了上面的列表、布局、表格,这些场景也超常用:
弹窗组件:自定义内容+控制逻辑
子组件Dialog.vue控制弹窗显示/隐藏,父组件自定义弹窗标题、内容,子组件把close方法传给父组件,父组件点“关闭”时调用:
<!-- 子组件 Dialog.vue -->
<template>
<div v-if="isShow" class="dialog-mask">
<div class="dialog-content">
<slot name="title" :title="title"></slot>
<slot name="body" :content="content"></slot>
<button @click="close">关闭弹窗</button>
<!-- 也可以把close方法传给父组件插槽 -->
<slot name="action" :close="close"></slot>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isShow = ref(true)
const title = ref('提示')
const content = ref('这是弹窗内容~')
const close = () => { isShow.value = false }
</script>
父组件用的时候,既可以用子组件的“关闭按钮”,也能自己加按钮调close:
<Dialog>
<template #title="{ title }">
<h2>{{ title }}</h2>
</template>
<template #body="{ content }">
<p>{{ content }}</p>
</template>
<template #action="{ close }">
<button @click="close">父组件自定义关闭</button>
</template>
</Dialog>
表单组件:自定义表单项+共享数据
子组件Form.vue封装表单验证、提交逻辑,父组件自定义输入框、下拉框,子组件把formData(表单数据)和handleSubmit(提交方法)传给父组件:
<!-- 子组件 Form.vue -->
<template>
<form @submit.prevent="handleSubmit">
<slot name="input" :formData="formData" :handleChange="handleChange"></slot>
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const formData = ref({ username: '', password: '' })
const handleChange = (field, value) => {
formData.value[field] = value
}
const handleSubmit = () => {
console.log('提交数据:', formData.value)
// 这里可以加接口请求逻辑
}
</script>
父组件自定义输入框,绑定handleChange:
<Form>
<template #input="{ formData, handleChange }">
<input
type="text"
placeholder="用户名"
:value="formData.username"
@input="e => handleChange('username', e.target.value)"
>
<input
type="password"
placeholder="密码"
:value="formData.password"
@input="e => handleChange('password', e.target.value)"
>
</template>
</Form>
常见坑点&解决办法
用slot传参时,这些错误新手常犯,避坑指南收好~
父组件“收不到参数”
- 检查子组件:
<slot>上有没有用v-bind传参?比如写成<slot msg="message">(没加v-bind),这时候传的是字符串"message",不是变量message的值!得写成<slot :msg="message">。 - 检查具名插槽:父组件的
#name和子组件<slot name="name">是否一致?比如子组件是name="header",父组件写成#head,肯定接不到。
参数是undefined
- 子组件数据没初始化:比如
const list = ref()(没给初始值),传给父组件就是undefined,要确保数据初始化,比如const list = ref([])。 - 异步数据没加载完:如果子组件数据是异步请求的,插槽可能在请求完成前就渲染了,可以用
v-if控制插槽显示,等数据到了再渲染:<template> <div v-if="dataLoaded"> <slot :list="dataList"></slot> </div> </template> <script setup> import { ref, onMounted } from 'vue' const dataList = ref([]) const dataLoaded = ref(false) onMounted(async () => { dataList.value = await fetchData() // 假设是异步请求 dataLoaded.value = true }) </script>
解构赋值报错
父组件用{ msg }解构,但子组件没传msg,会报“无法解构undefined”,解决办法:
- 给解构加默认值:
{ msg = '默认内容' }。 - 确保子组件传了该参数,或者父组件判断
slotProps.msg是否存在。
slot传参的核心逻辑
Vue3的slot传参,本质是子组件主动把数据/方法“抛”给父组件,父组件在插槽里“接住并用起来”,不管是默认插槽、具名插槽,核心步骤就两步:
- 子组件:用
<slot :参数名="数据"></slot>把数据绑到插槽上。 - 父组件:用
<template #name="接收对象">接住参数,然后自定义渲染。
掌握这两点,再结合解构、TypeScript、实际场景,就能把slot传参玩得飞起,让组件通信和自定义更灵活~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


