跳到內容

元件

Astro 元件是 Astro 專案的基石,只包含 HTML 模板,不依賴客戶端運行環境。你可以從副檔名辨別它:.astro

Astro 元件極其彈性,能包含共用 UI(像是頁首、個人資訊卡)、一小段 HTML(例如跟 SEO 有關的 <meta> 標籤),甚至整個網頁版面。

Astro 元件最重要的一點是:不在客戶端算繪。它們只在建置階段或伺服器端算繪(SSR)需要時轉換為 HTML。元件的 frontmatter 可放入 JavaScript 程式碼,它們不會送至使用者的瀏覽器。預設不包含任何 JavaScript 足跡,因此你的網站變得更快了。

如果需要客戶端的互動效果,可適時加入 HTML <script> 標籤UI 框架元件

Astro 元件由兩塊組成:元件腳本元件模板。兩者各司其職,攜手構築出既容易使用,又能滿足多樣開發需求的框架。

src/components/EmptyComponent.astro
---
// 元件腳本(JavaScript)
---
<!-- 元件模板(HTML + JS 表達式) -->

Astro 使用「程式碼圍欄」(---)區別元件腳本。如果你曾寫過 Markdown,你可能知道 frontmatter,兩者是相似的概念。Astro 元件腳本的構想就是受此概念啟發。

你可在元件腳本中撰寫算繪模板時需要用到的 JavaScript 程式碼。包含:

  • 匯入其他 Astro 元件
  • 匯入其他框架元件,例如 React
  • 匯入資料,例如 JSON 檔
  • 從 API 或資料庫抓取資料
  • 建立變數供模板使用
src/components/MyComponent.astro
---
import SomeAstroComponent from '../components/SomeAstroComponent.astro';
import SomeReactComponent from '../components/SomeReactComponent.jsx';
import someData from '../data/pokemon.json';
// 存取傳入元件的參數,例如 `<X title="Hello, World" />`
const { title } = Astro.props;
// 抓取外部資料,從非公開 API 或資料庫都可以
const data = await fetch('SOME_SECRET_API_URL/users').then(r => r.json());
---
<!-- 模板在這! -->

程式碼圍欄的設計理念是要「圍住」你寫的 JavaScript,確保它不會跑到前端應用程式、落入使用者手中。在圍欄裡可以安心地寫較耗資源或者敏感的程式碼(例如呼叫你的私有資料庫),不必擔心裡面的程式碼不小心出現在使用者的瀏覽器裡。

元件模板位於程式碼圍欄下方,它決定了最終產出的 HTML。

寫在這裡的 HTML 會被元件算繪,當元件被其他 Astro 頁面匯入使用時亦同。

不只一般的 HTML,Astro 的元件模板語法 也支援 JavaScript 表達式、Astro <style><script> 標籤、匯入的元件,以及特殊的 Astro 指令

src/components/MyFavoritePokemon.astro
---
// 這裡是元件腳本!
import Banner from '../components/Banner.astro';
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
const { title } = Astro.props;
---
<!-- 支援 HTML 註解! -->
{/* JS 註解也沒問題! */}
<Banner />
<h1>Hello, world!</h1>
<!-- 使用 props 和元件腳本的變數: -->
<p>{title}</p>
<!-- 透過 `client:` 指令引入其他 UI 框架元件:-->
<ReactPokemonComponent client:visible />
<!-- 在 HTML 使用 JavaScript 表達式,類似 JSX:-->
<ul>
{myFavoritePokemon.map((data) => <li>{data.name}</li>)}
</ul>
<!-- 使用模板指令合併 class name,字串甚至是物件都可以! -->
<p class:list={["add", "dynamic", {classNames: true}]} />

以元件為基礎的設計

標題為 以元件為基礎的設計

元件的設計理念是要能夠重複使用組合。你可以在元件裡使用其他元件,創造出更多更複雜的 UI。舉例來說,Button 元件可以組合成 ButtonGroup 元件:

src/components/ButtonGroup.astro
---
import Button from './Button.astro';
---
<div>
<Button title="Button 1" />
<Button title="Button 2" />
<Button title="Button 3" />
</div>

Astro 元件可以自訂和接受參數,供元件模板算繪 HTML 時使用。參數可以在 frontmatter 腳本透過全域的 Astro.props 存取。

以下範例中,元件接受傳入的 greetingname 參數。注意參數是從全域的 Astro.props 物件解構出來的。

src/components/GreetingHeadline.astro
---
// 用法:<GreetingHeadline greeting="Howdy" name="Partner" />
const { greeting, name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

其他 Astro 元件/版面/頁面,可以傳遞參數給 GreetingCard 元件:

src/components/GreetingCard.astro
---
import GreetingHeadline from './GreetingHeadline.astro';
const name = 'Astro';
---
<h1>歡迎卡片</h1>
<GreetingHeadline greeting="Hi" name={name} />
<p>祝你有個美好的一天!</p>

你可以用 TypeScript 自訂 Props 的型別,Astro 能自動偵測 frontmatter 的 Props,依此顯示警告/錯誤訊息。另外,從 Astro.props 解構參數時,能夠設定預設值。

src/components/GreetingHeadline.astro
---
interface Props {
name: string;
greeting?: string;
}
const { greeting = "Hello", name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

未傳入參數值時,可以設定預設值。

src/components/GreetingHeadline.astro
---
const { greeting = "Hello", name = "Astronaut" } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

<slot /> 元素是為外部 HTML 內容預留的位置,供子元素從其他檔案放入元件模板用。

預設情況下,元件會算繪所有透過 <slot /> 傳入的子元素。

src/components/Wrapper.astro
---
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>
src/pages/fred.astro
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
<h2>關於 Fred</h2>
<p>關於 Fred 的一些資訊。</p>
</Wrapper>

一整頁 HTML 內容被 <SomeLayoutComponent></SomeLayoutComponent> 標籤「包起來」,接著傳給其他元件算繪頁面所有元素——這個模式便是 Astro 版面元件的基礎。

Astro 元件可以放置具名插槽,讓你只傳入名字相符的 HTML 元素到指定插槽。

具名插槽使用 name 屬性:

src/components/Wrapper.astro
---
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 name="after-header" /> <!-- 有 `slot="after-header"` 屬性的子元素會跑來這 -->
<Logo />
<h1>{title}</h1>
<slot /> <!-- 沒有 `slot` 屬性的子元素,以及有 `slot="default"` 屬性的子元素會跑來這 -->
<Footer />
<slot name="after-footer" /> <!-- 有 `slot="after-footer"` 屬性的子元素會跑來這 -->
</div>

要把 HTML 內容放進特定的插槽,必須在子元素加上 slot 屬性指定對應的插槽,所有其他子元素則會被放入預設(不具名)的 <slot />

src/pages/fred.astro
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
<img src="https://my.photo/fred.jpg" slot="after-header" />
<h2>關於 Fred</h2>
<p>關於 Fred 的一些資訊。</p>
<p slot="after-footer">版權所有 2022</p>
</Wrapper>

若要一次傳遞多個 HTML 元素,但不想額外包一層 <div> 的話,可以在 Astro 的 <Fragment/> 元件使用 slot=""

src/components/CustomTable.astro
---
// 客製一個 table,使用具名插槽為 head 和 body 預留兩個位置
---
<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>

透過 slot="" 屬性,將表格內容放置於 "header""body"。還能單獨調整 HTML 元素的樣式。

src/components/StockTable.astro
---
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>

只能透過具名插槽傳送一層子元素,不能傳送巢狀元素,請特別留意。

插槽的備用內容

標題為 插槽的備用內容

插槽也能算繪備用內容。沒有對應的子元素傳遞過來時,<slot /> 元素會算繪自己的備用內容。

src/components/Wrapper.astro
---
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>
<p>這是我的備用內容,如果沒有傳子元素到 slot 時會顯示。</p>
</slot>
<Footer />
</div>

插槽可以轉移到其他元件。以建立巢狀版面為例:

src/layouts/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>
src/layouts/HomeLayout.astro
---
import BaseLayout from './BaseLayout.astro';
---
<BaseLayout>
<slot name="head" slot="head" />
<slot />
</BaseLayout>

傳給 HomeLayout 的預設插槽和 head 插槽,現在會被轉移到上層的 BaseLayout

src/pages/index.astro
---
import HomeLayout from '../layouts/HomeLayout.astro';
---
<HomeLayout>
<title slot="head">Astro</title>
<h1>Astro</h1>
</HomeLayout>

Astro 支援將 .html 檔案匯入成元件,或放置到 src/pages 子目錄裡當作頁面。如果你想在不使用框架的前提下重複利用現有網站程式碼,或想確保元件沒有動態功能的話,可以考慮使用 HTML 元件。

HTML 元件只能包含合法的 HTML,沒有 Astro 元件的特色功能:

繼續閱讀如何在 Astro 專案使用 UI 框架元件