Day 4 一直在講 .astro:layout 是 .astro、頁面是 .astro、被包進去的東西也是 .astro。但它到底是什麼?如果你寫過 React 或 Vue,它看起來很眼熟,眼熟到你會直接套用舊直覺,然後在某個地方踩空。
.astro 元件收 props、能拆分組合,這些跟 React 元件一樣。差別在執行位置:它在建置(或收到請求)時跑一次,交出 HTML,然後就不存在了。瀏覽器只收到它產出的 HTML,不會收到元件本身的 JavaScript;也就沒有由它維護的 state、re-render 或點擊事件。React 的心智模型是「元件是一段會在瀏覽器裡跑的程式」;Astro 的心智模型是「元件是一份會產出 HTML 的模板」。
.astro 元件怎麼收資料,跟 React、Vue 幾乎一模一樣。用 interface Props 宣告要收什麼,再從 Astro.props 解構出來,還能給預設值。官方文件的最小範例長這樣:
---
interface Props {
name: string;
greeting?: string;
}
const { greeting = "Hello", name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>
(來源:Astro 官方 Astro Components 文件,查證日 2026-07-21。)
greeting? 後面的問號代表選填,= "Hello" 是呼叫端沒給時的預設值。你在 React props 和 Vue 的 defineProps 都見過這套「型別宣告 + 解構 + 預設值」,概念一致。
Day 4 看過的 BaseLayout.astro 就是「一個會收 props 的 .astro 元件」:
---
interface Props {
title: string;
description?: string;
type?: 'website' | 'article';
// …還有 image、article
}
const { title, description = SITE.description, type = 'website' } = Astro.props;
---
title 必填、其餘選填,description 沒給就退回站台預設。就是 props,沒有新東西。
props 的寫法很熟悉,但執行位置不同:.astro 元件的邏輯跑在建置或伺服器端,不在瀏覽器。官方文件寫得很直接:
By default, Astro renders UI components to static HTML and CSS, stripping all client-side JavaScript.
(預設情況下,Astro 把 UI 元件渲染成靜態 HTML 與 CSS,並剝掉所有客戶端 JavaScript。)來源:Astro 官方 Islands 文件,查證日 2026-07-21。
寫在 .astro 那對 --- 之間的 JavaScript,只在「產 HTML 的時候」執行。執行完後只有 HTML 送到瀏覽器,所以這個元件無法在瀏覽器裡處理 state、useState 或 onClick,也沒有重新渲染。
實作專案裡的 src/components/BenchArticle.astro 是一個純內容樣本:
---
// frontmatter 只有註解,沒有 props、沒有 script
---
<article>
<h1>手沖咖啡的三個入門變數</h1>
<p>……純內容……</p>
<ul>
<li>……</li>
</ul>
</article>
BenchArticle.astro 只負責顯示文章,本來就不需要互動。它會產出 <article>,送到瀏覽器的 JavaScript 是 0 bytes;它就是一個完成的純內容元件。
React 元件會在瀏覽器裡持續運作;.astro 元件產出 HTML 後就結束,預設不留任何 JS。React 開發者讀 Astro 時,先把這個直覺調過來。
只要元件的工作是「把資料顯示出來」,它就不需要 JS。一張卡片、一段文章、一個 header、一個文章列表,這些都是純顯示,.astro 元件配零 JavaScript 就做完了。BenchArticle 就是這種。
需要「瀏覽器端的互動」時才送 JS,例如使用者點一下要有反應、要記住某個狀態,或要即時更新畫面。這類區塊用框架元件(Vue 或 React)來做;Day 8 會再談 islands。
你沒辦法在 .astro 元件上加 client:load。client:* 這類「要不要在瀏覽器啟動」的指令,只對框架元件有意義;.astro 元件不會在瀏覽器裡啟動,所以沒有這個選項。判斷一塊 UI 要不要送 JS,實際上就是在選 .astro 元件或框架 island。
專案裡的 ReactionButton 可以看出 JavaScript 是「一顆一顆元件選進來的」。在 bench/index.astro 寫成 <ReactionButton />,不加 client:,它就是靜態的、按鈕點不動、零 JS;在 bench/island.astro 寫成 <ReactionButton client:load />,加上指令才會啟動、送 JS,按鈕也才能點。這組對照會在 Day 8 再拆解。Astro 預設不會把 JavaScript 鋪滿整頁。
.astro frontmatter 裡用 window、document、onClick。 這段程式在伺服器端執行,當下沒有瀏覽器或 DOM。操作 DOM 是 Day 7(頁面內的 <script>)或 Day 8(island)的事。.astro 元件「加上」互動。 需要互動時,把那個區塊換成框架元件,再用 client:* 啟用客戶端 JavaScript。.astro 元件本身沒有「變得能互動」這個路徑。.astro,把 JS 留給真正需要的地方。.astro 元件跟 React 元件都用 props 傳資料,生命週期卻不同:.astro 元件跑一次、交出 HTML 就結束,瀏覽器裡不留 JS。面對一塊 UI,先問它是只要顯示,還是需要互動。只要顯示就用 .astro 元件配零 JS,需要互動才做成框架 island。.astro 元件不能加 client:*,因為它不在瀏覽器裡跑。
分清楚零 JS 的 .astro 元件後,下一個問題是樣式:CSS 寫在元件裡,會不會漏出去污染別的頁面?什麼時候該用共用的全域樣式?Day 6 會談 Astro 的樣式分層:scoped、global 還是 utility,以及各自適合放在哪裡。
本日程式碼:step-05|只看這天的改動:step-04...step-05