六维教程

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>
核心目的 列表/表格结构统一、内容由父组件定制
上一篇
Vue3 插槽基础
下一篇
Vue3 依赖注入