插槽 Slots:具名、默认与回退
本教程共 56 篇 · 第 9 篇 · 更新于 2026-08-07 · 约 11 分钟阅读
本节目标:学会用
<slot />把外部一整块 HTML 塞进组件的指定位置,分清默认插槽、具名插槽和回退内容,并理解插槽如何在嵌套布局间传递。
上一章的 Props,传的是“数据”(字符串、数字等)。但有时你想传的,是“一整段 HTML 结构”——比如一页文章的正文、一个卡片里的内容。Astro 用 插槽(slot) 来解决这个问题。slot 这个词不翻译,记成“插槽”就好:它是组件里预留的一个“空位”,等外面把内容插进来。
默认插槽:最简单的注入
最基础的插槽就是一个 <slot /> 标签,它像个占位符。所有传给这个组件的子元素,默认都会渲染到 <slot /> 所在的位置。
看一个“包装组件”:
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';
const { title } = Astro.props;
---
<div id="content-wrapper">
<Header />
<Logo />
<h1>{title}</h1>
<slot /> <!-- 外部传进来的内容会出现在这里 -->
<Footer />
</div>
使用时,写在 <Wrapper>...</Wrapper> 标签之间的内容,就会落到那个 <slot /> 上:
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred 的页面">
<h2>关于 Fred 的一切</h2>
<p>这里有些关于 Fred 的内容。</p>
</Wrapper>
最终页面里,<h2> 和 <p> 被安插到了 Header、Logo、Footer 中间。这就是布局组件(layout)的原理——一整页 HTML 被 <SomeLayout></SomeLayout> 包住,送进布局里公共的页头页脚之间。
Note本章的插槽是 Astro 自带能力。Web 框架(如 Vue)里也有 slot 概念,写法类似,但 Astro 的 slot 不依赖任何框架,是纯模板特性。
具名插槽:把内容放到指定位置
默认插槽只能容纳“没有指定名字”的内容。如果你想让不同内容去不同位置,就用 具名插槽(named slot):给插槽起个名字,外面用 slot="名字" 指定谁去哪儿。
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';
const { title } = Astro.props;
---
<div id="content-wrapper">
<Header />
<!-- 带 slot="after-header" 属性的子元素来这里 -->
<slot name="after-header" />
<Logo />
<h1>{title}</h1>
<!-- 没带 slot、或带 slot="default" 的来这里 -->
<slot />
<Footer />
<!-- 带 slot="after-footer" 的来这里 -->
<slot name="after-footer" />
</div>
调用时,给子元素加 slot 属性挑位置:
<Wrapper title="Fred 的页面">
<img src="https://my.photo/fred.jpg" slot="after-header" />
<h2>关于 Fred 的一切</h2>
<p>这里有些关于 Fred 的内容。</p>
<p slot="after-footer">版权所有 2022</p>
</Wrapper>
规则很直观:带 slot="after-header" 的去第一个具名插槽;带 slot="after-footer" 的去最后一个;剩下没标名字的,全进默认 <slot />。
Warning具名插槽必须是组件的“直接子元素”。你不能把它藏在某一层嵌套标签里再传——Astro 只认最外层的
slot="名字"属性。
用 Fragment 传多段内容
想往一个具名插槽里塞好几行 HTML,又不想额外套个 <div> 破坏结构,可以用 Astro 的 <Fragment> 组件,给它加 slot 属性:
---
// 一个自定义表格,表头和表体各留一个具名插槽
---
<table class="bg-white">
<thead class="sticky top-0 bg-white"><slot name="header" /></thead>
<tbody class="[&_tr:nth-child(odd)]:bg-gray-100"><slot name="body" /></tbody>
</table>
使用时用 <Fragment slot="..."> 包住多行内容:
---
import CustomTable from './CustomTable.astro';
---
<CustomTable>
<Fragment slot="header">
<tr><th>商品名</th><th>库存</th></tr>
</Fragment>
<Fragment slot="body">
<tr><td>拖鞋</td><td>64</td></tr>
<tr><td>靴子</td><td>32</td></tr>
<tr><td>运动鞋</td><td class="text-red-500">0</td></tr>
</Fragment>
</CustomTable>
Fragment 本身不生成任何标签,只是个“分组容器”,方便你把多段内容整批送进某个插槽。
回退内容:插槽为空时显示啥
插槽还能放 回退内容(fallback content):当外面没传任何匹配的子元素时,<slot> 就显示它自己包含的占位内容。
<div id="content-wrapper">
<Header />
<Logo />
<h1>{title}</h1>
<slot>
<p>如果没有内容传进来,就显示这句回退文字</p>
</slot>
<Footer />
</div>
如果调用方忘了传内容,用户至少看到一句提示,而不是一片空白。
这里有个容易踩的坑:回退内容只在“根本找不到对应插槽元素”时才出现。如果你传了一个空的插槽(比如 <Wrapper></Wrapper> 啥也没写),Astro 会把它当成“有一个空插槽”,这时回退内容不会顶上。换句话说,回退是给“完全没传”兜底,不是给“传了空的”兜底。
插槽传递:在嵌套布局间接力
插槽还能“往下传”,这在做多层布局时很有用。比如你有个基础布局 BaseLayout,它定义了 head 具名插槽和默认插槽:
---
// BaseLayout.astro
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<slot name="head" />
</head>
<body>
<slot />
</body>
</html>
再做一个 HomeLayout,它本身也接收插槽,并把它们“转交”给 BaseLayout:
---
import BaseLayout from './BaseLayout.astro';
---
<BaseLayout>
<slot name="head" slot="head" />
<slot />
</BaseLayout>
于是,传给 HomeLayout 的默认内容和 head 插槽,会被原样接力到父级 BaseLayout。页面里这样用:
---
import HomeLayout from '../layouts/HomeLayout.astro';
---
<HomeLayout>
<title slot="head">Astro</title>
<h1>Astro</h1>
</HomeLayout>
<title slot="head"> 经过 HomeLayout 中转,最终落到 BaseLayout 的 <slot name="head" />;<h1> 则进入默认插槽。这种“插槽接力”让你能一层层叠加布局,而不用重复写 HTML 骨架。
插槽和 Props 怎么选
初学时容易搞混:到底什么时候用插槽,什么时候用 Props?一句话区分:
- 传“数据”(字符串、数字、对象)用 Props;
- 传“一整块 HTML 结构”(可能是段落、列表、图片的任意组合)用插槽。
两者常常配合:Props 控制开关和文案,插槽容纳主体内容。比如一个“文章卡片”,标题文字用 Props 传,卡片里那一坨正文(段落 + 图片 + 引用)用插槽塞。这样卡片既能复用,内容又灵活。
Tip记不住就问自己:我要传的是“一个值”,还是“一段 HTML”?值用 Props,HTML 用插槽。
默认插槽和具名插槽一起用
实际写组件,默认插槽和具名插槽常同时出现。比如一个“卡片”,头部、主体、页脚各就各位:
---
// Card.astro
---
<div class="card">
<div class="card-head"><slot name="head" /></div>
<div class="card-body"><slot /></div>
<div class="card-foot"><slot name="foot">默认页脚文字</slot></div>
</div>
调用时三种插槽一次用上:
<Card>
<h3 slot="head">卡片标题</h3>
<p>这是卡片的正文内容,进默认插槽。</p>
<p slot="foot">自定义页脚</p>
</Card>
这里 head 进具名插槽,foot 进另一个具名插槽(若没传就显示回退文字),中间没标名字的 <p> 进默认插槽。一个组件同时玩转三种插槽,布局既规整又灵活。这种“具名 + 默认 + 回退”的组合,正是布局类组件的常见套路,掌握后你能搭出非常规整的页面骨架。
一个完整页面是怎么用插槽的
把前面的布局概念拼起来,一个真实博客页面的用法是这样的。先写一个基础布局:
---
// BaseLayout.astro
interface Props {
title: string;
}
const { title } = Astro.props;
---
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>{title}</title>
<slot name="head" />
</head>
<body>
<header>这是所有页面共用的页头</header>
<main>
<slot /> <!-- 每篇文章的正文从这里进来 -->
</main>
<footer>这是所有页面共用的页脚</footer>
</body>
</html>
然后写一篇文章页面,把内容和额外的 <head> 信息塞进对应插槽:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="我的第一篇">
<meta slot="head" name="description" content="这是摘要" />
<article>
<h1>我的第一篇</h1>
<p>正文写在这里,会落到默认插槽,被包在页头和页脚中间。</p>
</article>
</BaseLayout>
siteTitle 是 Props,<meta> 走具名 head 插槽,整段 <article> 走默认插槽。一套布局,全站复用,这就是布局组件的核心价值。
插槽内容可以是「多段」
默认插槽和具名插槽都能接收「好几段」内容,不限于一个标签。比如默认插槽里你写了三段 <p>、一个 <img>,它们会按顺序全部塞进 <slot /> 所在位置。Astro 不会只挑第一个,也不会多套一层标签——你写几段,就进几段,原样排放。
唯一要记的规矩还是那条:具名插槽的属性 slot="名字" 必须写在「直接子元素」上。如果你把内容套进一个 <div slot="x">,那这个 div 才是直接子元素,它里面的东西整体进那个具名插槽——这通常也符合预期,只是别把它藏在更深的嵌套里指望 Astro 穿透去找。
Note在
.mdx文件里也能用插槽:当你在 MDX 里导入一个 Astro 布局组件并包住内容时,正文同样走默认插槽。这是写文档站时很常用的套路。
小结
插槽解决的是“把一整块 HTML 注入组件”的需求:<slot /> 是默认插槽;加 name 变具名插槽,外面用 slot="名字" 指路;插槽里写的内容是回退兜底;<Fragment> 能整批传多段;嵌套布局间还能用 slot="x" slot="x" 接力传递。配合上一章的 Props,你就掌握了组件之间“传数据”和“传结构”的两套本领。下一章讲怎么给组件加上真正的交互——客户端脚本与事件处理。