เมื่อใดควรใช้ Island
ปรัชญาหลักของ Astro นั้นเรียบง่าย: เริ่มต้นด้วย static แล้ว opt เข้า JS เฉพาะเมื่อต้องการ นี่คือตรงข้ามกับ React SPA ที่ทุกอย่างเป็น JavaScript จนกว่าคุณจะลงแรงตัด JavaScript ออก
กฎ default-static
หัวข้อที่มีชื่อว่า “กฎ default-static”ถามคำถามนี้สำหรับทุก component บนเพจ:
“Component นี้ตอบสนองต่อ user interaction หรือเปลี่ยนแปลงตามเวลาใน browser หรือไม่?”
ถ้า ใช่ — ทำให้เป็น island ด้วย client:* directive ที่เหมาะสม
ถ้า ไม่ — คงไว้เป็น .astro component หรือ React component ที่ไม่มี directive (render เป็น HTML แบบ static)
// In a React/Next.js app everything is a component.// Making something "static" requires extra effort.export default function BlogPage({ post, related }) { return ( <div> {/* These are all in the React tree, all hydrated */} <Header /> {/* purely presentational */} <ArticleContent post={post} /> {/* purely presentational */} <LikeButton postId={post.id} /> {/* interactive */} <ShareMenu url={post.url} /> {/* interactive */} <RelatedPosts posts={related} />{/* purely presentational */} </div> );}---// In Astro, ask "does this need JS?" for each piece.import Header from '../components/Header.astro'; // no JSimport ArticleContent from '../components/ArticleContent.astro'; // no JSimport LikeButton from '../components/LikeButton.jsx'; // needs JSimport ShareMenu from '../components/ShareMenu.jsx'; // needs JSimport RelatedPosts from '../components/RelatedPosts.astro'; // no JSconst { post, related } = Astro.props;---<Header /><ArticleContent post={post} /><LikeButton client:visible postId={post.id} /><ShareMenu client:idle url={post.url} /><RelatedPosts posts={related} />ในหน้าบล็อกโพสต์ทั่วไป มีเพียงสอง islands (LikeButton และ ShareMenu) ที่ส่ง JavaScript ทุกอย่างอื่นเป็น HTML ที่ไม่มี JS
Islands แยกตัวออกจากกัน
หัวข้อที่มีชื่อว่า “Islands แยกตัวออกจากกัน”นี่คือความแตกต่างทางสถาปัตยกรรมที่สำคัญที่สุดจาก React SPA ในแอป React มี component tree เดียว React Context ไหลจาก top-level Provider ลงไปยัง descendant ใดก็ได้ ไม่ว่าจะลึกแค่ไหน
ใน Astro แต่ละ island เป็น React root แยกกัน Context ไม่ข้าม island boundaries
// This does NOT work across islands://// .astro page:// <ThemeProvider client:load> ← island A// <ThemedButton client:load /> ← island B (separate root!)//// ThemeProvider's context is not visible to ThemedButton.การทำงานรอบข้อจำกัดนี้
หัวข้อที่มีชื่อว่า “การทำงานรอบข้อจำกัดนี้”ตัวเลือกที่ 1 — ห่อทั้งสองใน single island:
---import ThemedApp from '../components/ThemedApp.jsx';---<ThemedApp client:load />// ThemedApp.jsx — one island, one React root, context worksimport { ThemeProvider } from './ThemeContext';import ThemedButton from './ThemedButton';
export default function ThemedApp() { return ( <ThemeProvider> <ThemedButton /> </ThemeProvider> );}ตัวเลือกที่ 2 — ใช้ shared signal/store ภายนอก React:
Libraries อย่าง nanostores (tiny store ที่ Astro แนะนำ) ให้ islands แชร์ state โดยไม่ต้องมี React root ร่วมกัน:
import { atom } from 'nanostores';export const theme = atom('light');// island Aimport { useStore } from '@nanostores/react';import { theme } from '../stores/theme';export default function ThemeToggle() { const t = useStore(theme); return <button onClick={() => theme.set(t === 'light' ? 'dark' : 'light')}>{t}</button>;}// island B — reads the same storeimport { useStore } from '@nanostores/react';import { theme } from '../stores/theme';export default function ThemedButton() { const t = useStore(theme); return <button class={t}>Click me</button>;}Checklist การตัดสินใจ
หัวข้อที่มีชื่อว่า “Checklist การตัดสินใจ”| Component | ต้องการ hydration? | ใช้อะไร |
|---|---|---|
| Navigation header | ไม่ (แค่ links) | .astro component |
| Hero image | ไม่ | <img> ใน .astro |
| Article body | ไม่ | .astro component |
| ปุ่ม Like / upvote | ใช่ — click handler | React island client:visible |
| Comment section | ใช่ — fetch + render | React island client:visible |
| Mobile menu toggle | ใช่ — open/close state | React island client:media |
| Page layout | ไม่ | .astro layout |
| Date formatter | ไม่ (build-time) | .astro หรือ React แบบ static |