iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
Modern Web

用 Astro 打造 Content-first 前端網站:30 天從靜態內容到會員、資料庫與選型(3rd)系列 第 7

只想加一顆按鈕、一點互動,需要動用框架嗎?Astro 頁面裡的原生 script

  • 分享至 

  • xImage
  •  

Day 5 講過 .astro 元件不在瀏覽器裡跑,互動可以交給 framework island。只想加一顆計數按鈕或一個展開收合時,載入整包 Vue 或 React island 反而太重;一段原生 ... 就夠了。

計數器、copy 按鈕、展開收合這類小互動,不一定要用 island。判斷時,可以把「這塊要多少 Java...」想成三個檔位:

  • 第 1 檔(預設):0 JS,純 .astro 靜態 HTML。大部分內容都停在這裡(Day 5)。
  • 第 2 檔(這一篇):一段原生 ...,做小互動,沒有反應式狀態。
  • 第 3 檔(Day 8):framework island,有 state、有反應式、UI 複雜。

互動需要多少 Java...,就用對應的檔位,不必每次都直接跳到第 3 檔。

JS 寫在哪裡,才會在瀏覽器執行?

Day 5 的結論是:.astro frontmatter(--- 之間)的程式跑在伺服器,build 或收到請求時執行一次就結束。把 onClick 寫在元素上看似合理,但在 Astro 不會動。官方文件也示範了這種寫法:

沿用 Day 5 的執行界線。handleClick 定義在 frontmatter,只會在伺服器端執行,不會送到瀏覽器綁定事件。要讓程式在瀏覽器執行,就把它寫進 ... 標籤,再用 addEventListener 掛事件:

(來源:Astro 官方 astro-syntax 與 client-side-...s 文件,查證日 2026-07-21。)

要在伺服器算的東西寫 frontmatter,要在瀏覽器跑的東西寫 ...

Astro 會幫你處理 ...

Astro 預設會先處理頁面裡的 ...,再送給瀏覽器。官方文件列出其中幾項:

Astro will process... automatically enables Type... support, bundles imports, and processes ...s as type modules. Small ...s may also be inlined.
(Astro 會處理它,自動支援 Type...、打包 import、以 type module 的方式處理;小的腳本也可能直接內嵌。)

來源:Astro 官方 client-side-...s 文件,查證日 2026-07-21。

所以你可以直接寫現代 JS 或 Type...、直接 import 本地檔案或 npm 套件,Astro 會幫你打包。處理過的 ... 也在 module 作用域,不是全域;變數不會污染其他腳本,腳本跑一次就結束。

is:inline 可以退出這套處理:加上去後,這段 ... 會原封不動輸出,不轉 Type...、不解析 import;如果它放在會被重複使用的元件裡,每個實例還會各自複製一份。一般情況保留 Astro 的預設處理即可。

我看過一個上線中的多語系品牌官網:package.json 裡裝了 jQuery,頁面的 ... 卻在等待它出現在全域:

同一段 ... 對本地模組用了 import,卻等著 package.json 已列出的 jQuery 出現在全域。整個專案沒有任何地方把 $ 賦值到 window,所以 setTimeout 每 100 毫秒遞迴一次,永遠等不到。

這種寫法延續自 jQuery 常見的使用方式:在 <head> 放一支 CDN ...,全域自然就有 $。把這個習慣搬進有打包工具的專案時,最少改動的做法就是「假設它已經在全域」,程式碼看起來也還是能跑。

在 Astro 裡,更適合的做法是 import $ from 'jquery',讓 Astro 把它打包進這支 ...。套件沒裝好會在 build 時就報錯,不會留下安靜跑在背景的輪詢迴圈。

不過,那段程式只負責在按下按鈕時更換背景色,用原生 ... 修改 style.backgroundColor 就能完成。是否需要整個函式庫,仍要看這個互動落在哪個檔位。

用一段 ... 完成兩個小互動

範例頁 src/pages/demos/native-....astro(網址 /demos/native-...)包含兩個小互動,都用純原生 ... 實作。先看計數器:

按鈕行為放在這段 ...:抓 DOM、掛上 click 監聽,再更新文字。frontmatter 不負責這段瀏覽器行為,整個計數器就這幾行。

第二個是展開/收合,同樣先抓元素、掛監聽、切換 hidden 屬性,並更新 aria-expanded,讓輔助工具也讀得懂:

這頁已通過 build,也能實際操作。兩個互動都停在第 2 檔,能回應操作,沒有需要框架管理的反應式狀態。

界線:什麼時候該換到 island?

原生 ... 和 island 各有適合的互動範圍:

  • 適合原生 ...:小、狀態單純或根本沒狀態、一次性的 DOM 行為。計數器、展開收合、copy 按鈕、捲動回頂端、漸進增強(沒 JS 也能用,有 JS 更順)。
  • 該換成 island:互動有反應式狀態,狀態一變就要更新畫面很多處,或 UI 複雜、需要跨元件共享同一份狀態。若仍用原生 ...,就得在許多地方手動 textContent = ...、手動加減屬性來追蹤那份狀態,等於自己手刻一個小框架。

當你開始在好幾個地方手動同步「DOM 畫面」和「一份資料」,就該升到第 3 檔(island)。只有一兩處要改,一段 ... 就夠了;為了一顆按鈕載入整包 Vue,則超過了互動的實際需求。

幾個容易踩的點

  • 在 frontmatter 寫 onClick 或使用 window frontmatter 跑在伺服器,沒有 DOM,也沒有點擊事件。要在瀏覽器發生的行為,一律寫進 ...
  • ... 當成裸 ...。 Astro 預設會用 module 作用域處理、打包並支援 Type...;只有需要原封不動輸出時,才加 is:inline
  • 讓需要 island 的互動繼續使用 ... 短期內可以運作,但狀態增加後,手動 DOM 操作會彼此難以同步。這時應依互動需求換成 island。

今日驗收

第 2 檔的驗收有三項。要在瀏覽器跑的程式應寫進 ...;frontmatter 留給伺服器端程式。Astro 預設會處理 ...(打包、Type...、module 作用域),只有要退出處理時才用 is:inline。遇到互動需求時,你也應該能判斷它適合第 2 檔(一段原生 ...),還是該升到第 3 檔(framework island)。

下一步

第 3 檔的 island 怎麼運作?一顆 Vue 元件加上 client:load 之後,整頁會變成 SPA,還是只有那一塊送出 JS?Day 8 會從「哪一塊、什麼時候才 hydrate」說明 island 的運作方式。

本日程式碼:step-07|只看這天的改動:step-06...step-07

PS 因為無法成功貼文的關係,所以把 script 相關的內容都拿掉了,...指 script。有興趣的讀者請參考 repo 的內容。


上一篇
元件元件裡的 CSS 會不會漏出去污染別頁?Astro 樣式怎麼分 scoped、global、utility?
下一篇
把 Vue 元件變成 Astro 島:client:* 開關怎麼挑?
系列文
用 Astro 打造 Content-first 前端網站:30 天從靜態內容到會員、資料庫與選型(3rd)9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言