人人都会AI编程

作用域插槽:子传父数据的插槽实现

更新时间:2026-07-09

在 10.1 节介绍的默认插槽和具名插槽解决了“父组件往子组件里塞内容”的问题,但实际开发中还有一种很常见的需求:子组件内部有一些数据,希望交给父组件来决定如何渲染。这就是作用域插槽(Scoped Slots)的用武之地。

它解决什么问题

假设你在封装一个商品列表组件 <ProductList>,子组件内部已经拿到了商品数组,但每个卡片的 UI 样式在不同页面里可能完全不同——比如首页只显示名称和价格,搜索页要显示缩略图和评分,管理后台要显示编辑和删除按钮。你不可能为每一种样式都写一个专用组件,更合理的方式是:子组件提供数据,父组件决定展示方式

作用域插槽的本质是“带有参数的插槽”。子组件在插槽上绑定一些数据,父组件通过一个模板变量接收这些数据,然后自由渲染。

基础语法

子组件(ProductList.vue):用 <slot :属性名="数据"> 的形式向外暴露数据:

<template>
  <div class="product-list">
    <div v-for="product in products" :key="product.id">
      <!-- 通过插槽向外抛出 product 对象 -->
      <slot name="card" :product="product" />
    </div>
  </div>
</template>

<script setup>
defineProps(['products'])
</script>

父组件:用 v-slot:插槽名="变量名" 接收数据,然后在模板中自由使用:

<template>
  <ProductList :products="productList">
    <!-- 接收子组件抛出的 product 数据 -->
    <template v-slot:card="{ product }">
      <div class="card">
        <h3>{{ product.name }}</h3>
        <p>¥{{ product.price }}</p>
      </div>
    </template>
  </ProductList>
</template>

如果只有一个默认插槽且需要传递数据,可以直接用 v-slot="slotProps"

<!-- 子组件 -->
<slot :user="currentUser" />

<!-- 父组件 -->
<MyComponent v-slot="{ user }">
  <span>{{ user.name }}</span>
</MyComponent>

实战示例:表格列的自定义渲染

作用域插槽最常见的场景之一就是表格组件封装。表格组件负责分页、排序、数据循环,但每列的渲染方式交给使用者决定:

子组件(DataTable.vue)

<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">{{ col.title }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in data" :key="row.id">
        <td v-for="col in columns" :key="col.key">
          <!-- 动态插槽名,将整行数据 row 抛出去 -->
          <slot :name="col.key" :row="row">
            <!-- 默认直接显示字段值 -->
            {{ row[col.key] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script setup>
defineProps(['columns', 'data'])
</script>

父组件

<template>
  <DataTable :columns="columns" :data="users">
    <!-- 状态列:自定义成带颜色的圆点 -->
    <template v-slot:status="{ row }">
      <span :style="{ color: row.status === 'active' ? 'green' : 'gray' }">
        ● {{ row.status }}
      </span>
    </template>

    <!-- 操作列:自定义成按钮 -->
    <template v-slot:action="{ row }">
      <button @click="handleEdit(row)">编辑</button>
      <button @click="handleDelete(row.id)">删除</button>
    </template>
  </DataTable>
</template>

<script setup>
const columns = [
  { key: 'name', title: '姓名' },
  { key: 'email', title: '邮箱' },
  { key: 'status', title: '状态' },
  { key: 'action', title: '操作' }
]
const users = [
  { id: 1, name: 'Alice', email: 'a@example.com', status: 'active' },
  { id: 2, name: 'Bob', email: 'b@example.com', status: 'inactive' }
]
</script>

这个例子中,表格组件本身不关心“状态怎么展示”或“操作按钮长什么样”,它只负责遍历数据并抛出每一行的数据。父组件拿到 row 对象后,可以用任何喜欢的方式渲染那一列。这就是作用域插槽的核心价值:让复用组件真正做到了“样式与数据分离”,数据归子组件管,渲染权归父组件

与普通插槽的本质区别

  • 普通插槽<slot></slot> 接收的是父组件写好的静态或动态内容,子组件无法向其中注入数据。
  • 作用域插槽<slot :data="something"></slot> 相当于子组件声明了一个“回调接口”,告诉父组件:“我这里有一些数据,你想怎么用就怎么用,我给你传过去。”

这使得组件封装边界更清晰:子组件负责获取/处理数据(如 API 请求、格式化),父组件负责 UI 呈现。当需要更换 UI 时,只需修改父组件的插槽模板,子组件逻辑完全不动。

一个小提醒

  • 解构语法 v-slot="{ user }" 可以让你直接拿到需要的数据,不用每次 slotProps.user
  • Vue 3 中,v-slot 只能用在 <template> 标签或组件标签上(当只使用默认插槽时可以直接写在组件标签上)。
  • 作用域插槽的数据是响应式的,子组件的数据变了,父组件会同步更新。