Vue3 作用域插槽
子组件 UserList 自己从接口拿了一份用户列表,父组件想定制每一行显示成什么样,比如名字加粗、加个序号。
普通插槽可以让父组件往子组件里塞内容,但塞进去的内容只能用父组件自己的数据。
列表数据在子组件手里,父组件在插槽里写 {{ user.name }},user 根本不存在:
<UserList>
<p>{{ user.name }}</p> <!-- user 是哪来的?报错 -->
</UserList>
矛盾就在这里,数据在子组件里,展示却由父组件决定,想让父组件的内容用上子组件的数据,得让子组件把数据递出来,这就是作用域插槽。
子组件端:把数据递出来
子组件的 <slot> 可以挂属性,这些属性会随插槽内容一起送到父组件:
<!-- 子组件 UserList.vue -->
<script setup>
import { ref } from 'vue'
const users = ref([
{ id: 1, name: '小明', age: 18 },
{ id: 2, name: '小红', age: 20 }
])
</script>
<template>
<ul>
<li v-for="(user, i) in users" :key="user.id">
<!-- 把每一项 user 和下标递出去 -->
<slot :user="user" :index="i" />
</li>
</ul>
</template>
给 <slot> 添加的属性就叫”作用域插槽的 props”,它和组件的 props 不是一回事,但概念相通,都是”把数据交给对方”。
父组件端:用 v-slot 接住
父组件用 v-slot 声明接收,可以全量收 v-slot="slotProps",也可以解构 #default="{ user }":
<!-- 父组件 -->
<script setup>
import UserList from './components/UserList.vue'
</script>
<template>
<!-- 全量接收 -->
<UserList v-slot="slotProps">
<p>{{ slotProps.user.name }}(第 {{ slotProps.index + 1 }} 个)</p>
</UserList>
<!-- 更常用的解构写法 -->
<UserList #default="{ user }">
<p>{{ user.name }},{{ user.age }} 岁</p>
</UserList>
</template>
大白话,子组件说”我这里有一份数据,你们拿去自定义渲染”,父组件说”我要,解构出来用”。
写 v-slot="props" 还是 #default="{...}"?
两者等价,#default 更常见也更好读。有多个插槽时,每个都用 <template #名字="{...}">,默认插槽还可以省略 #default 直接写 v-slot。
插槽里的数据是”实时”的吗?是的。
子组件的数据变化,用到的插槽内容会跟着响应式更新,反过来,别在插槽内容里直接改传入的数据,遵循单向数据流。
具名作用域插槽:命名 + 传数据
作用域和具名可以叠加,语法是 <template #名字="{ 变量 }">:
<!-- 子组件 DataTable.vue -->
<template>
<table>
<tr v-for="row in rows" :key="row.id">
<td><slot name="cell-name" :value="row.name" /></td>
<td><slot name="cell-price" :value="row.price" /></td>
</tr>
</table>
</template>
<!-- 父组件,不同列用不同数据定制 -->
<DataTable>
<template #cell-name="{ value }">{{ value.toUpperCase() }}</template>
<template #cell-price="{ value }">¥{{ value }}</template>
</DataTable>
典型场景:列表项可定制
作用域插槽最常被用于”结构同一、样式可换”的列表和表格。
列表组件提供数据,父组件决定每项怎么展示,这也是 Element Plus 这类组件库表格列的底层原理。
<!-- 同一份列表,两种渲染 -->
<ListWrap v-slot="{ item }">
<b>{{ item.name }}</b>
</ListWrap>
<ListWrap v-slot="{ item }">
<i>{{ item.desc }}</i>
</ListWrap>
速查卡片
| 场景 | 写法 |
|---|---|
| 子组件传数据给插槽 | <slot :user="user" :index="i" /> |
| 父组件接收(推荐) | #default="{ user }" |
| 父组件接收(全量) | v-slot="slotProps" |
| 具名作用域插槽 | <template #cell-name="{ value }">…</template> |
| 核心目的 | 列表/表格结构统一、内容由父组件定制 |