跳到內容

版面

版面是一種 Astro 元件,用來建立可重複使用的 UI 結構,例如頁面模板。

我們習慣以「版面」稱呼在不同頁面共用的 Astro 元件,例如頁首、導覽列、頁尾這種 UI 元素。典型的 Astro 版面元件為 Astro、Markdown 或 MDX 頁面提供:

  • 頁面殼層<html><head><body> 標籤)
  • 供頁面內容嵌入的插槽 <slot />

但其實版面元件沒什麼特別的!它們和其他 Astro 元件一樣,可以接受參數匯入並使用其他元件,也能包含 UI 框架元件客戶端腳本。甚至可當作局部 UI 模板,不需要提供整個頁面。

版面元件通常放在專案的 src/layouts 目錄,但這不是強制規定,可以自由選擇要放在哪裡。你甚至可以把它們跟頁面放在一起,只要在版面名稱加上 _ 前綴即可。

src/layouts/MySiteLayout.astro
---
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
const { title } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<BaseHead title={title}/>
</head>
<body>
<nav>
<a href="#">Home</a>
<a href="#">Posts</a>
<a href="#">Contact</a>
</nav>
<h1>{title}</h1>
<article>
<slot /> <!-- 你的內容會嵌在這裡 -->
</article>
<Footer />
</body>
</html>
src/pages/index.astro
---
import MySiteLayout from '../layouts/MySiteLayout.astro';
---
<MySiteLayout title="Home Page">
<p>我的網頁內容被包在一個版面裡!</p>
</MySiteLayout>
進一步了解插槽

版面對無法自訂頁面格式的 Markdown 和 MDX 頁面來說很實用。

Astro 特殊的 layout frontmatter 屬性可以指定要把哪一個 .astro 元件當作頁面版面。

src/pages/page.md
---
layout: ../layouts/BaseLayout.astro
title: "Hello, World!"
author: "Matthew Phillips"
date: "09 Aug 2022"
---
Astro 版面元件可以透過參數存取所有 frontmatter 屬性。
`layout` 是 Astro 提供的特殊屬性。
`src/pages/` 的 Markdown 和 MDX 檔案都能使用該屬性。

給 Markdown 或 MDX 頁面用的版面通常包含:

  1. frontmatter 參數,能夠存取 Markdown 或 MDX 頁面的 frontmatter 和其他資料。
  2. 預設的 <slot />,指名頁面的 Markdown/MDX 內容要在哪個位置算繪。
src/layouts/BaseLayout.astro
---
// 1. 透過 frontmatter 參數存取 frontmatter 和其他資料
const { frontmatter } = Astro.props;
---
<html>
<head>
<!-- 這裡可以放其他 Head 元素,例如樣式和 meta 標籤。 -->
<title>{frontmatter.title}</title>
</head>
<body>
<!-- 這裡可以放其他 UI 元件,例如共用的頁首和頁尾。 -->
<h1>{frontmatter.title} by {frontmatter.author}</h1>
<!-- 2. 算繪後的 HTML 會傳入預設插槽 -->
<slot />
<p>Written on: {frontmatter.date}</p>
</body>
</html>

你可藉由 MarkdownLayoutProps or MDXLayoutProps 設定版面的 Props 型別

src/layouts/BaseLayout.astro
---
import type { MarkdownLayoutProps } from 'astro';
type Props = MarkdownLayoutProps<{
// 在此定義 frontmatter 參數的型別
title: string;
author: string;
date: string;
}>;
// 現在,`frontmatter`、`url` 與其他 Markdown 版面屬性
// 都能在保證型別安全的情況下存取
const { frontmatter, url } = Astro.props;
---
<html>
<head>
<link rel="canonical" href={new URL(url, Astro.site).pathname}>
<title>{frontmatter.title}</title>
</head>
<body>
<h1>{frontmatter.title} by {frontmatter.author}</h1>
<slot />
<p>Written on: {frontmatter.date}</p>
</body>
</html>

Markdown 版面參數

標題為 Markdown 版面參數

Markdown/MDX 版面能透過 Astro.props 存取下列資訊:

  • file:檔案的絕對路徑 (例如 /home/user/projects/.../file.md)。
  • url:如果是頁面的話,即頁面網址(例如 /zh-tw/guides/markdown-content)。
  • frontmatter:Markdown 或 MDX 文件中的所有 frontmatter。
    • frontmatter.file:同最上層的 file 屬性。
    • frontmatter.url:同最上層的 url 屬性。
  • headings:Markdown 或 MDX 文件中的標題(h1 -> h6)列表,包含對應的 metadata。其型別為 { depth: number; slug: string; text: string }[]
  • (Markdown 專屬)rawContent():取得 Markdown 原始內容的函式,回傳值格式為字串。
  • (Markdown 專屬)compiledContent():取得 Markdown 編譯後內容的函式,回傳值格式為 HTML 字串。

舉例來說,Markdown 撰寫的部落格文章可以把下列 Astro.props 物件傳給它的版面:

Astro.props = {
file: "/home/user/projects/.../file.md",
url: "/zh-tw/guides/markdown-content/",
frontmatter: {
/** 部落格文章的 frontmatter */
title: "Astro 0.18 Release",
date: "Tuesday, July 27 2021",
author: "Matthew Phillips",
description: "Astro 0.18 is our biggest release since Astro launch.",
/** 產生的值 */
file: "/home/user/projects/.../file.md",
url: "/zh-tw/guides/markdown-content/"
},
headings: [
{
"depth": 1,
"text": "Astro 0.18 Release",
"slug": "astro-018-release"
},
{
"depth": 2,
"text": "Responsive partial hydration",
"slug": "responsive-partial-hydration"
}
/* ... */
],
/** 只能在 Markdown 使用 */
rawContent: () => "# Astro 0.18 Release\nA little over a month ago, the first public beta [...]",
compiledContent: () => "<h1>Astro 0.18 Release</h1>\n<p>A little over a month ago, the first public beta [...]</p>",
}

手動匯入版面(MDX)

標題為 手動匯入版面(MDX)

需要傳遞資訊到 MDX 版面,但該版面不存在(或無法存在)frontmatter 時,可以匯入 <Layout /> 元件,再像其他元件一樣透過參數傳遞給它:

src/pages/posts/first-post.mdx
---
layout: ../../layouts/BaseLayout.astro
title: 'My first MDX post'
publishDate: '21 September 2022'
---
import BaseLayout from '../../layouts/BaseLayout.astro';
export function fancyJsHelper() {
return "Try doing that with YAML!";
}
<BaseLayout title={frontmatter.title} fancyJsHelper={fancyJsHelper}>
Welcome to my new Astro blog, using MDX!
</BaseLayout>

如此一來,版面便能透過 Astro.props 存取數值,而 MDX 內容則會嵌入到包含 <slot /> 的頁面中:

src/layouts/BaseLayout.astro
---
const { title, fancyJsHelper } = Astro.props;
---
<!-- -->
<h1>{title}</h1>
<slot /> <!-- 你的內容會嵌在這裡 -->
<p>{fancyJsHelper()}</p>
<!-- -->
關於 Astro 對 Markdown 和 MDX 的支援,請參考 Markdown/MDX 指南

.md.mdx.astro 之間共用相同版面

標題為 在 .md、.mdx、.astro 之間共用相同版面

Astro 版面可以接收 .md.mdx 檔的 frontmatter 物件,以及從 .astro 檔傳入的任何具名參數。

以下範例中,版面的頁面標題會顯示從 frontmatter YAML title 屬性傳入的值,或從 Astro 元件傳入的 title 屬性:

src/components/MyLayout.astro
---
const { title } = Astro.props.frontmatter || Astro.props;
---
<html>
<head></head>
<body>
<h1>{title}</h1>
<slot />
</body>
</html>

版面元件不需要包含整頁 HTML 內容。可以將版面拆成更小的元件,並搭配使用版面元件建立更彈性的頁面模板。在不同版面共用程式碼時,這個模式十分實用。

舉例來說,BlogPostLayout.astro 元件可以為部落格文章的標題、日期,以及作者設定樣式。接著,在整個站台共用的 BaseLayout.astro 可以處理剩下的頁面模板,像導覽列、頁尾、SEO meta 標籤、全域樣式、字型等。你也可以從文章接收參數,再傳遞到其他版面,就像跟其他巢狀元件互動一樣。

src/layouts/BlogPostLayout.astro
---
import BaseLayout from './BaseLayout.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout url={frontmatter.url}>
<h1>{frontmatter.title}</h1>
<h2>Post author: {frontmatter.author}</h2>
<slot />
</BaseLayout>