首页 / Vue3 入门教程 / 插槽(下)

Vue3 入门教程

插槽(下)

本教程共 40 篇 · 第 18 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

VueVue3 入门教程作用域插槽无渲染组件插槽PropsRenderless

18. 插槽(下)

本节目标:掌握作用域插槽,学会让子组件把数据”回传”给插槽内容,实现逻辑和样式的分离。

作用域插槽解决了什么问题

上一节说过,插槽内容只能访问父组件的作用域。但有些场景需要子组件把数据传给插槽用。

比如一个 <FancyList> 组件负责加载数据,但每一项的渲染方式由父组件决定。数据在子组件里,渲染在父组件里——这就需要作用域插槽。

基本用法

子组件给 <slot> 传属性:

<!-- ChildComponent.vue -->
<template>
  <!-- 子组件通过属性把数据传给插槽 -->
  <slot text="hello" :count="1"></slot>
</template>

父组件用 v-slot 接收这些数据:

<!-- v-slot 的值是接收 prop 的对象 -->
<ChildComponent v-slot="receivedProps">
  {{ receivedProps.text }} {{ receivedProps.count }}
</ChildComponent>

用函数来理解:

// 子组件把数据传给 slot 函数
function ChildComponent(slots) {
  return slots.default({ text: 'hello', count: 1 })
}

// 父组件提供一个函数接收数据
ChildComponent({
  default: (receivedProps) => {
    return `${receivedProps.text} ${receivedProps.count}`
  }
})

解构语法

和函数参数一样,v-slot 也支持解构:

<ChildComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</ChildComponent>

实际例子:列表组件

子组件负责数据和循环:

<!-- FancyList.vue -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <!-- 把 item 对象的所有属性传给插槽 -->
      <slot name="item" v-bind="item"></slot>
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, body: '内容一', username: '张三', likes: 10 },
  { id: 2, body: '内容二', username: '李四', likes: 20 }
])
</script>

父组件决定每一项怎么显示:

<FancyList>
  <!-- 解构接收子组件传来的 item 对象 -->
  <template #item="{ body, username, likes }">
    <div class="item">
      <p>{{ body }}</p>
      <p>by {{ username }} | {{ likes }} 赞</p>
    </div>
  </template>
</FancyList>

v-bind="item" 把 item 对象的所有属性都传给插槽,父组件想用什么就拿什么。

Tip

作用域插槽的核心思路:子组件管逻辑(数据获取、分页),父组件管样式(渲染方式)。

命名作用域插槽

命名插槽也能传 props:

<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>

  <template #footer="footerProps">
    {{ footerProps }}
  </template>
</MyComponent>

子组件定义:

<slot name="header" message="hello"></slot>

注意:name 不会出现在 props 里,它只是插槽的标识符。

混合使用时的注意事项

当同时有默认插槽和命名插槽时,默认插槽必须显式写 <template #default>

<!-- ❌ 编译错误 -->
<MyComponent v-slot="{ message }">
  {{ message }}
  <template #footer>
    <!-- message 不属于这里 -->
    <p>{{ message }}</p>
  </template>
</MyComponent>

<!-- ✅ 正确写法 -->
<MyComponent>
  <template #default="{ message }">
    <p>{{ message }}</p>
  </template>

  <template #footer>
    <p>页脚信息</p>
  </template>
</MyComponent>

无渲染组件

如果把作用域插槽推到极致,就有了无渲染组件——只封装逻辑,不渲染任何 DOM。

比如一个追踪鼠标位置的组件:

<!-- 无渲染组件:只提供数据,视觉完全由父组件决定 -->
<MouseTracker v-slot="{ x, y }">
  鼠标位置: {{ x }}, {{ y }}
</MouseTracker>

<MouseTracker> 内部没有 template,只通过插槽把 xy 传出去。

Note

无渲染组件的思路很有启发,但大多数场景用组合式函数(Composables)更高效,不需要额外的组件嵌套。作用域插槽更适合”逻辑 + 视觉组合”的场景,比如前面的 <FancyList>

什么时候用作用域插槽

场景适合用
子组件管数据,父组件管渲染作用域插槽
纯逻辑复用,不需要 DOM组合式函数
通用 UI 结构复用命名插槽

本节回顾

  • 作用域插槽让子组件把数据传给插槽内容使用
  • 子组件用属性传递,父组件用 v-slot 接收
  • 支持解构语法,和函数参数类似
  • 命名作用域插槽语法:v-slot:name="props"
  • 无渲染组件只封装逻辑,视觉完全交给父组件