默认插槽与具名插槽
插槽与组件组合 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
插槽(slot)解决组件复用的另一半问题:props 传的是数据,插槽传的是模板片段。这一节掌握默认插槽、具名插槽、插槽后备内容的写法,并理解「父组件提供结构、子组件提供容器」的分工。
问题场景
你要做一个卡片组件,起初只放标题和正文,用 props 传字符串就够了。后来产品要求:有的卡片要放按钮,有的要放图片,有的要放一个自定义的统计组件。用 props 传 HTML 字符串既危险又难维护;给每种变体加一个 prop 会让组件接口爆炸。插槽就是为这种「结构不确定」的场景设计的。
核心解释
默认插槽:子组件在模板里放 <slot /> 作为占位,父组件把内容写在子组件标签之间:
<!-- components/Panel.vue -->
<template>
<section class="panel">
<slot>这里是默认内容,父组件没传时显示</slot>
</section>
</template>
父组件在标签之间写内容即可填入默认插槽:<Panel><p>这段内容会渲染到 slot 的位置</p></Panel>;不传时显示子组件里写的后备内容。
具名插槽:一个组件有多个插入点时,用 name 区分:
<!-- components/Card.vue -->
<template>
<article class="card">
<header>
<slot name="header">
<h3>默认标题</h3>
</slot>
</header>
<div class="body">
<slot /> <!-- 默认插槽 -->
</div>
<footer v-if="$slots.footer">
<slot name="footer" />
</footer>
</article>
</template>
父组件用 <template #header> 填具名插槽,# 是 v-slot: 的简写:
父组件用 <template #header> 与 <template #footer> 填具名插槽,默认插槽的内容直接写在组件标签之间,例如 <Card><template #header><h3>进度</h3></template><p>已完成 8 / 20 门课</p></Card>。
三个细节:默认插槽的简写是 <template #default>;$slots.footer 可以判断父组件是否提供了某个插槽,用于决定要不要渲染外层包裹元素;插槽内容在父组件的作用域里编译——它能访问父组件的数据,不能直接访问子组件的数据(这正是下一节作用域插槽要解决的)。
插槽不是 props 的替代品,而是补充:数据用 props(可校验、可追踪),结构用插槽(灵活、组合)。判断标准:如果父组件想控制的是「显示什么内容」,用插槽;如果只是想传一个值,用 props。
示例拆解
一个「布局容器」组件,用具名插槽固定骨架,把内容区交给页面:
<!-- components/PageLayout.vue -->
<template>
<div class="layout">
<aside class="sidebar">
<slot name="sidebar">
<nav>默认侧边栏</nav>
</slot>
</aside>
<main class="content">
<slot name="toolbar" />
<slot />
</main>
</div>
</template>
<style scoped>
.layout { display: grid; grid-template-columns: 220px 1fr; gap: 16px; }
.sidebar { border-right: 1px solid #e5e7eb; padding-right: 12px; }
</style>
<!-- 页面使用 -->
<script setup>
import { ref } from "vue";
import PageLayout from "./components/PageLayout.vue";
const keyword = ref("");
</script>
<template>
<PageLayout>
<template #sidebar>
<ul>
<li>课程</li>
<li>练习</li>
</ul>
</template>
<template #toolbar>
<input v-model="keyword" placeholder="搜索" />
</template>
<p>正文内容,keyword 在父组件作用域:{{ keyword }}</p>
</PageLayout>
</template>
注意 keyword 是父组件的数据,插槽内容能直接用它——插槽模板属于父组件的编译作用域。如果子组件想把自己内部的数据交给父组件渲染,就需要作用域插槽。
动手任务
- 给
Card加一个#media具名插槽,用于放图片或视频,父组件不传时不占位。 - 用
$slots判断实现「只有传了 footer 才渲染 footer 的边框样式」。 - 写一个
Alert组件,默认插槽放消息内容,#icon插槽放图标,不传图标时用默认的 ⚠️。
常见错误
- 在子组件模板里想访问父组件插槽内容的数据:插槽内容在父作用域编译,子组件拿不到。需要数据就往下传或用作用域插槽。
- 具名插槽忘了
<template>:直接写<div #header>会报错,v-slot只能用在<template>或组件标签上(默认插槽可直接写在组件标签上)。 - 默认插槽和具名插槽混用顺序错误:隐式默认插槽内容应放在所有具名插槽之后,否则 Vue 会把前面的内容当作默认插槽,导致顺序混乱。
- 用插槽替代一切 props:简单数据也用插槽会让接口不可校验、模板冗长。数据走 props,结构走插槽。
检查与复盘
- 能写出默认插槽、具名插槽、后备内容三件套
- 能解释插槽内容的编译作用域归属
- 能判断一个需求该用 props 还是插槽
参考资料
- Vue 官方文档「插槽」:默认/具名/作用域插槽完整规范
- Vue 官方文档「组件基础」:组件组合的基本模式
读完这一课了?
标记完成后,工作台会安排接下来的学习。