首页 / Astro 教程 / 插槽 Slots:具名、默认与回退

Astro 教程

插槽 Slots:具名、默认与回退

本教程共 56 篇 · 第 9 篇 · 更新于 2026-08-07 · 约 11 分钟阅读

AstroAstro 教程插槽slot具名插槽默认插槽回退内容

本节目标:学会用 <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,你就掌握了组件之间“传数据”和“传结构”的两套本领。下一章讲怎么给组件加上真正的交互——客户端脚本与事件处理。