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,只通过插槽把 x 和 y 传出去。
Note无渲染组件的思路很有启发,但大多数场景用组合式函数(Composables)更高效,不需要额外的组件嵌套。作用域插槽更适合”逻辑 + 视觉组合”的场景,比如前面的
<FancyList>。
什么时候用作用域插槽
| 场景 | 适合用 |
|---|---|
| 子组件管数据,父组件管渲染 | 作用域插槽 |
| 纯逻辑复用,不需要 DOM | 组合式函数 |
| 通用 UI 结构复用 | 命名插槽 |
本节回顾
- 作用域插槽让子组件把数据传给插槽内容使用
- 子组件用属性传递,父组件用
v-slot接收 - 支持解构语法,和函数参数类似
- 命名作用域插槽语法:
v-slot:name="props" - 无渲染组件只封装逻辑,视觉完全交给父组件