Code前端首页关于Code前端联系我们

Vue3里slot怎么传参?默认、具名、作用域插槽传参门道全在这

terry 5小时前 阅读数 16 #SEO
文章标签 Vue3插槽传参

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,分headercontent两个插槽,分别传标题、文章数据:

<!-- 子组件 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会自动提示msgstringlistnumber[],减少拼写错误~

实际项目里,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传参,本质是子组件主动把数据/方法“抛”给父组件,父组件在插槽里“接住并用起来”,不管是默认插槽、具名插槽,核心步骤就两步:

  1. 子组件:用<slot :参数名="数据"></slot>把数据绑到插槽上。
  2. 父组件:用<template #name="接收对象">接住参数,然后自定义渲染。

掌握这两点,再结合解构、TypeScript、实际场景,就能把slot传参玩得飞起,让组件通信和自定义更灵活~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门