跳到主要内容

3.6 插槽与组件扩展

本章目标: 给组件预留内容扩展位置,理解“谁提供数据,谁决定显示”。

props 传数据,slot 传内容​

如果弹窗需要 title 字符串,用 prop 就够。如果弹窗标题区域需要图标、链接和其他组件,插槽比传 HTML 字符串更合适。

可类比 Blazor 的 RenderFragment:父组件提供模板内容,子组件决定放在哪里。两者并非共享同一个运行机制。

默认与具名插槽​

src/components/EmptyState.vue
<template>
<section class="empty" aria-label="空状态">
<h2><slot name="title">暂无数据</slot></h2>
<div><slot>还没有可显示的内容。</slot></div>
<div><slot name="actions" /></div>
</section>
</template>

<style scoped>
.empty { padding: 2rem 0; text-align: center; }
</style>
src/App.vue
<script setup lang="ts">
import EmptyState from './components/EmptyState.vue';
</script>

<template>
<EmptyState>
<template #title>没有匹配的任务</template>
<p>可以调整搜索词或新增任务。</p>
<template #actions>
<a href="https://cn.vuejs.org/">继续学习 Vue</a>
</template>
</EmptyState>
</template>

#title 是 v-slot:title 的简写。未传内容时使用子组件的后备内容。

内容属于父组件作用域​

父组件写在插槽里的表达式访问父组件的状态。它不会自动访问子组件内部的变量。需要让子组件把数据交出来时用作用域插槽。

src/components/TaskList.vue
<script setup lang="ts">
import type { TaskItem } from '../types/task';
const props = defineProps<{ tasks: TaskItem[] }>();
</script>

<template>
<ul>
<li v-for="task in props.tasks" :key="task.id">
<slot :task="task">{{ task.title }}</slot>
</li>
</ul>
</template>

父组件已经导入 TaskList 并声明 tasks 时:

<TaskList :tasks="tasks" v-slot="{ task }">
<strong>{{ task.title }}</strong>
<span>{{ task.isDone ? '已完成' : '待完成' }}</span>
</TaskList>

TaskList 提供每个 task,父组件决定如何显示。这是表格的列模板、选择器的选项模板常见的实现方式。

控制扩展范围​

先用 props 和明确事件满足业务需求,再在需要模板定制的位置加 slot。过多插槽会让组件结构难以维护;没有使用需求时不必提前开放所有位置。

练习​

给 EmptyState 加一个 description 具名插槽,默认文字为“请稍后重试”。父组件用一个段落替换默认内容。

参考答案

子组件加入 <slot name="description">请稍后重试</slot>;父组件传入 <template #description><p>请清空筛选条件。</p></template>。默认插槽和具名插槽可以一起使用。

参考:插槽。