0%

已完成 0/12 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 7 关 含代码 约 21 分钟

布局与插槽:封装页面通用外壳、<slot /> 体系与多层布局嵌套

布局与插槽:封装页面通用外壳、<slot /> 体系与多层布局嵌套

在上一关中,我们虽然把导航栏和页脚抽离成了组件,但如果你仔细看 index.astro、about.astro 和 blog.astro,你会发现它们仍然在反复手写一模一样的外壳代码:

<!doctype html>
<html lang="zh-cn">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <title>{pageTitle}</title>
  </head>
  <body>
    <Header />
    <!-- 只有这里的正文才是每个页面独特的! -->
    <Footer />
  </body>
</html>

不仅如此,我们在 src/pages/posts/ 写的 Markdown 博客文章,目前甚至连导航和页脚都没有,光秃秃的像一张白纸!

为了让每个页面和每篇博客文章都能共享统一优雅的“外衣”,Astro 提供了专门的解决方案:布局组件(Layout Components)与插槽(Slots)。


1. 核心概念与新术语白话解析

(1) 布局组件(Layout Component)

  • 它是什么:存放在 src/layouts/ 目录下的特殊 Astro 组件(如 BaseLayout.astro)。
  • 为什么需要它:用来作为整个网页的“母版外壳”,统一掌管 <!doctype html>、<head> 标签、全局 CSS 引入、Header 导航与 Footer 页脚。
  • 与普通组件的区别:普通组件通常是页面里的一小块局部零件(如一个按钮);而布局组件是包裹整个页面的大容器。

(2) 插槽(<slot /> 占位符)

  • 它是什么:布局组件内部放置的一个专用占位标签 <slot />。
  • 为什么需要它:布局就像一个相框,<slot /> 就是相框中间留空的相片位置。当你在页面中使用 <BaseLayout><p>今天天气真好</p></BaseLayout> 时,<p> 标签就会自动被塞进相框中间由 <slot /> 预留的位置上!
【插槽组装过程】:
页面源码:
<BaseLayout pageTitle="首页">
   <h2>欢迎光临!</h2>        ──────┐
   <p>这是我的最新动态。</p>  ──────┼───► 自动注入到母版的 <slot /> 占位处
</BaseLayout>                  ──────┘

最终浏览器渲染结果:
<!doctype html>
<html>
  <head>...</head>
  <body>
    <Header />
    <main>
       <h2>欢迎光临!</h2>
       <p>这是我的最新动态。</p>
    </main>
    <Footer />
  </body>
</html>

(3) 具名插槽(Named Slots)

  • 它是什么:带有名字的插槽,例如 <slot name="after-title" />。
  • 为什么需要它:如果一个布局需要把子内容分别塞到两三个不同的位置(例如主体放中间,侧边栏放右边),就可以用带有名字的插槽进行多点定向投递。外部传入时只需给元素加上 slot="after-title" 属性即可。

(4) Markdown 布局绑定(layout frontmatter 属性)

  • 它是什么:在 Markdown 文件的 frontmatter 里直接指定 layout: ../../layouts/MarkdownPostLayout.astro。
  • 为什么需要它:Markdown 文件本身没有 HTML 标签外壳。通过这行配置,Astro 会自动把整篇 Markdown 文章渲染后的正文注入到该布局的 <slot /> 中,同时把 frontmatter 里的属性作为 Astro.props.frontmatter 传递给布局组件!

2. 动手实操:创建全局基础布局 BaseLayout.astro

第一步:创建 BaseLayout.astro

在 src/ 目录下新建 layouts 文件夹,并在其中新建 BaseLayout.astro(路径:src/layouts/BaseLayout.astro):

---
// src/layouts/BaseLayout.astro
import '../styles/global.css';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';

interface Props {
  pageTitle: string;
}

const { pageTitle } = Astro.props;
---

<!doctype html>
<html lang="zh-cn">
  <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} />
    <title>{pageTitle}</title>
  </head>
  <body>
    <Header />

    <h1>{pageTitle}</h1>

    <!-- 这里的 <slot /> 负责承接每个页面专属的正文内容 -->
    <slot />

    <Footer />
  </body>
</html>

第二步:重构首页 src/pages/index.astro

打开 src/pages/index.astro,将其代码精简重构为:

---
// src/pages/index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
const pageTitle = "首页";
---

<BaseLayout pageTitle={pageTitle}>
  <h2>我超棒的博客副标题</h2>
  <p>欢迎来到我的 Astro 学习网站。这个页面现在完全基于 BaseLayout 布局构建!</p>
</BaseLayout>

保存后查看首页,你会发现原本冗长的 <html>、<head> 全部消失了,代码变得无比清爽,而浏览器的呈现依然完美无缺!

请以同样的方式重构 src/pages/blog.astro:

---
// src/pages/blog.astro
import BaseLayout from '../layouts/BaseLayout.astro';
const pageTitle = "我的 Astro 学习博客";
---

<BaseLayout pageTitle={pageTitle}>
  <p>在这里,我将分享我的 Astro 学习之旅。</p>
  <ul>
    <li><a href="/posts/post-1/">文章 1:我的第一篇博客文章</a></li>
    <li><a href="/posts/post-2/">文章 2:我的第二篇博客文章</a></li>
    <li><a href="/posts/post-3/">文章 3:我的第三篇博客文章</a></li>
  </ul>
</BaseLayout>

3. 为 Markdown 博客定制专用布局:MarkdownPostLayout.astro

现在我们来解决博客文章排版简陋的问题。我们希望所有 Markdown 博客文章都拥有统一的文章信息头:显示发布时间、作者名字、封面图以及标签列表。

第一步:创建博客文章布局

在 src/layouts/ 目录下新建 MarkdownPostLayout.astro:

---
// src/layouts/MarkdownPostLayout.astro
import BaseLayout from './BaseLayout.astro';

const { frontmatter } = Astro.props;
---

<!-- 布局嵌套:文章布局包裹在 BaseLayout 内部! -->
<BaseLayout pageTitle={frontmatter.title}>
  <div class="post-meta">
    <p>发表于:{frontmatter.pubDate.toString().slice(0, 10)}</p>
    <p><em>{frontmatter.description}</em></p>
    <p>作者:{frontmatter.author}</p>
  </div>

  {frontmatter.image && (
    <img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
  )}

  <hr />

  <!-- 这里渲染 Markdown 文章本身的文字内容 -->
  <slot />
</BaseLayout>

<style>
  .post-meta {
    background: #e2e8f0;
    padding: 0.75rem 1rem;
    border-radius: 6px;
    margin-bottom: 1.5rem;
  }
  .post-meta p {
    margin: 0.25rem 0;
  }
</style>

第二步:在 Markdown 文章中指定布局

打开 src/pages/posts/post-1.md,在它的 frontmatter 里加上 layout 指向刚才的布局:

---
layout: ../../layouts/MarkdownPostLayout.astro
title: '我的第一篇博客文章'
pubDate: 2026-10-19
description: '这是我 Astro 博客的第一篇文章。'
author: 'Astro 学习者'
tags: ["astro", "blogging", "learning in public"]
---
# 我的第一篇博客文章

欢迎来到我的 Astro 新博客!在这里,我将分享我建立新网站的学习历程。

## 我做了什么
1. **安装 Astro**:首先,我创建了一个新的 Astro 项目。
2. **制作页面**:然后我学习了如何通过创建 `.astro` 文件来制作页面。
3. **发表博客文章**:这是我的第一篇 Markdown 文章!

请对 post-2.md 和 post-3.md 做同样的更新。

切回浏览器访问 http://localhost:4321/posts/post-1/,你会看到惊艳的效果:

  • 文章不仅拥有了顶部的完整导航栏和底部的社交页脚;
  • 还自动渲染出了灰底元信息框,展示了格式化后的发布日期和作者名;
  • 正文内容整整齐齐地排在分割线下方!

4. 深入理解:多层布局嵌套(Layout Nesting)的魅力

注意我们在 MarkdownPostLayout.astro 内部所做的设计: 它没有自己重新写一遍 <!doctype html>,而是直接在外面套了一层 <BaseLayout pageTitle={frontmatter.title}>!

这种多层嵌套模式带来了极高的维护性:

  • 如果未来你想修改全站的页头导航,只需修改 BaseLayout.astro,所有普通页面和所有博客文章都会同步更新;
  • 如果你想调整博客文章的作者栏排版,只需修改 MarkdownPostLayout.astro,普通页面的排版丝毫不会受到打扰。

5. 本关小结

  1. 布局母版:src/layouts/BaseLayout.astro 统一包裹外壳标签,消除整站重复。
  2. 插槽注入:<slot /> 是承接子页面正文的传送门。
  3. Markdown 赋能:在 frontmatter 中使用 layout 属性绑定布局,数据自动从 Astro.props.frontmatter 传入。
  4. 两全其美嵌套:文章布局嵌套基础布局,层次分明,职责独立。

目前,我们的网站已经初具规模,但所有页面的交互都是靠整页跳转完成的。如果我们需要在移动端屏幕上制作一个“点击展开/收起的汉堡菜单”,或者制作一个“深色模式切换按钮”,该怎么做呢?必须装一个庞大的前端框架吗? 下一关,我们将学习 Astro 官方推荐的无需框架的客户端原生交互开发!

下一站:客户端原生交互:无需前端框架的 <script> 标签与深色模式切换 →
© 2026 Astro 入门与基础