元件
Astro 元件是 Astro 專案的基石,只包含 HTML 模板,不依賴客戶端運行環境,使用 .astro 副檔名。
:::note如果你懂 HTML,你就有足夠的知識寫出你的第一個 Astro 元件了。
Astro 元件極其彈性,Astro 元件能只有一小段 HTML(例如跟 SEO 有關的 <meta> 標籤),也可以是共用的 UI 元素,像是頁首、個人資訊卡。元件甚至能包含整個網頁版面,當元件在特殊的 src/pages/ 資料夾時,元件可以就是整個網頁版面。
Astro 元件最重要的一點是:不在客戶端算繪。它們只在建置階段或在需要時轉換為 HTML。元件的 frontmatter 可放入 JavaScript 程式碼,它們不會送至使用者的瀏覽器。預設不包含任何 JavaScript 足跡,因此你的網站變得更快了。
如果需要客戶端的互動效果,可適時加入 HTML <script> 標籤 (EN),或加入 UI 框架元件 (EN)做為「客戶端群島」。
對於需要算繪個人化或動態內容的元件,你可以加入伺服器端指令 (EN)延後元件的伺服器端算繪。這些「伺服器端群島」會在有空的時候才算繪內容,不會耽誤到整個頁面的載入。
元件架構
Astro 元件由兩塊組成:元件腳本和元件模板。兩者各司其職,攜手構築出既容易使用,又能滿足多樣開發需求的框架。
---
// 元件腳本(JavaScript)
---
<!-- 元件模板(HTML + JS 表達式) -->元件腳本
Astro 使用「程式碼圍欄」(---)區別元件腳本。如果你曾寫過 Markdown,你可能知道 frontmatter,兩者是相似的概念。Astro 元件腳本的構想就是受此概念啟發。
你可在元件腳本中撰寫算繪模板時需要用到的 JavaScript 程式碼。包含:
- 匯入其他 Astro 元件
- 匯入其他框架元件,例如 React
- 匯入資料,例如 JSON 檔
- 從 API 或資料庫抓取資料
- 建立變數供模板使用
---
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,確保它不會跑到前端應用程式、落入使用者手中。在圍欄裡可以安心地寫較耗資源或者敏感的程式碼(例如呼叫你的私有資料庫),不必擔心裡面的程式碼不小心出現在使用者的瀏覽器裡。
:::noteAstro 元件腳本使用 TypeScript,這讓您可以為 JavaScript 增加額外的語法,以支援編輯器工具和錯誤檢查功能。
元件模板
元件模板位於程式碼圍欄下方,它決定了最終產出的 HTML。
寫在這裡的 HTML 會被元件算繪,當元件被其他 Astro 頁面匯入使用時亦同。
不只一般的 HTML,Astro 的元件模板語法 也支援 JavaScript 表達式、Astro <style> (EN) 和 <script> (EN) 標籤、匯入的元件,以及特殊的 Astro 指令 (EN)。定義在元件腳本的資料與值可以用在元件模板以產生動態建立的 HTML。
---
// 這裡是元件腳本!
import Banner from '../components/Banner.astro';
import Avatar from '../components/Avatar.astro';
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
const { title } = Astro.props;
---
<!-- 支援 HTML 註解! -->
{/* JS 註解也沒問題! */}
<Banner />
<h1>Hello, world!</h1>
<!-- 使用 props 和元件腳本的變數: -->
<p>{title}</p>
<!-- 延後算繪元件並提供備用的載入中內容: -->
<Avatar server:defer>
<svg slot="fallback" class="generic-avatar" transition:name="avatar">...</svg>
</Avatar>
<!-- 透過 `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 元件:
---
import Button from './Button.astro';
---
<div>
<Button title="Button 1" />
<Button title="Button 2" />
<Button title="Button 3" />
</div>元件參數
Astro 元件可以自訂和接受參數,供元件模板算繪 HTML 時使用。參數可以在 frontmatter 腳本透過全域的 Astro.props 存取。
以下範例中,元件接受傳入的 greeting 和 name 參數。注意參數是從全域的 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 /> 傳入的子元素。
:::note參數 是傳遞給 Astro 元件的屬性,可以在元件中透過 Astro.props 存取,而 插槽 則是在它們被寫下的位置算繪 HTML 子元素。:::
---
// 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.slots 工具函式以了解更多存取與算繪插槽內容的方式。
具名插槽
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="after-header"` 屬性的子元素會跑來這 -->
<slot name="after-header" />
<Logo />
<h1>{title}</h1>
<!-- 沒有 `slot` 屬性的子元素,以及有 `slot="default"` 屬性的子元素會跑來這 -->
<slot />
<Footer />
<!-- 有 `slot="after-footer"` 屬性的子元素會跑來這 -->
<slot name="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>:::tip在子元素加上 slot="my-slot",藉此將它們傳遞到對應的 <slot name="my-slot" /> 預留位置。:::
若要一次傳遞多個 HTML 元素,但不想額外包一層 <div> 的話,可以在 Astro 的 <Fragment/> 元件使用 slot="":
---
// 客製一個 table,使用具名插槽為 header 和 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 元素的樣式。
---
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>只能透過具名插槽傳送一層子元素,不能傳送巢狀元素,請特別留意。
:::tip具名插槽也可傳遞到 UI 框架元件 (EN)!:::
:::noteAstro 插槽名稱無法動態產生(例如透過 map 函式操作)。想在 UI 框架元件裡動態產生插槽名稱的話,最好還是讓框架本身去處理。:::
插槽的備用內容
插槽也能算繪備用內容。沒有對應的子元素傳遞過來時,<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>備用內容只會在沒有符合 slot="name" 屬性的元素被傳遞到具名插槽時顯示。
當插槽元素存在,但是沒有內容可以傳遞時,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>:::notename 和 slot 屬性都可將具名插槽轉移至其他元件。:::
傳給 HomeLayout 的預設插槽和 head 插槽,現在會被轉移到上層的 BaseLayout。
// src/pages/index.astro
---
import HomeLayout from '../layouts/HomeLayout.astro';
---
<HomeLayout>
<title slot="head">Astro</title>
<h1>Astro</h1>
</HomeLayout>HTML 元件
Astro 支援將 .html 檔案匯入成元件,或放置到 src/pages 子目錄裡當作頁面。如果你想在不使用框架的前提下重複利用現有網站程式碼,或想確保元件沒有動態功能的話,可以考慮使用 HTML 元件。
HTML 元件只能包含合法的 HTML,沒有 Astro 元件的特色功能:
- 不支援 frontmatter,伺服器端匯入,以及動態表達式
<script>標籤不會被打包,行為同is:inline指令 (EN)- 只能連結
public/資料夾裡的靜態資源
:::noteHTML 元件裡的 <slot /> 元素可以正常運作。如果想用 HTML Web Component 插槽 元素的話,在 <slot> 元素加上 is:inline。:::