แชร์ State ข้าม Islands
ปัญหาเรื่องการแยกตัว
หัวข้อที่มีชื่อว่า “ปัญหาเรื่องการแยกตัว”island hydrate อย่างเป็นอิสระ — นั่นแหละที่ทำให้ต้นทุนถูก แต่มีผลข้างเคียงที่คนมาจาก SPA มักสะดุด: island สองตัวไม่แชร์ state กันอัตโนมัติ useState ของ React ใน island หนึ่งมองไม่เห็นจากอีก island หนึ่ง แม้จะสร้างจาก framework เดียวกันก็ตาม ไม่มี app tree ร่วมกัน แต่ละ island เป็น root เล็ก ๆ ของตัวเอง
แล้ว island “add to cart” กับ island “cart count” ที่ header จะ sync กันได้ยังไง? คุณมีเครื่องมือสามอย่าง เรียงจากง่ายที่สุดไปทรงพลังที่สุด
1. ส่ง serializable props จาก server
หัวข้อที่มีชื่อว่า “1. ส่ง serializable props จาก server”ถ้าข้อมูลรู้อยู่แล้วตอนหน้า render คำตอบที่ง่ายที่สุดคือคำนวณครั้งเดียวในไฟล์ .astro แล้วส่งให้แต่ละ island เป็น prop ไม่ต้องประสานงานข้าม island เลย — server คือแหล่งข้อมูลร่วม
---import CartBadge from '../components/CartBadge.jsx';const initialCount = await getCartCount(Astro.request);---<CartBadge count={initialCount} client:load />อันนี้ครอบคลุม state เริ่มต้น แต่ไม่ช่วยตอนที่ island หนึ่งต้อง อัปเดต อีกตัวตอน runtime — นั่นคือสิ่งที่ store มีไว้
2. nanostores สำหรับ state ที่แชร์ข้าม island
หัวข้อที่มีชื่อว่า “2. nanostores สำหรับ state ที่แชร์ข้าม island”เมื่อ island ต้องตอบสนองต่อกันตอน runtime ให้ใช้ shared store ที่อยู่นอก island ตัวใดตัวหนึ่ง เอกสารของ Astro แนะนำ nanostores — ขนาดเล็กมาก ไม่ผูกกับ framework ใด และมี adapter สำหรับแต่ละ framework (@nanostores/react, @nanostores/vue, …) คุณนิยาม store ครั้งเดียว แล้ว island ไหนก็อ่านและเขียน store นี้ได้
import { atom } from 'nanostores';export const cartCount = atom(0);// AddToCart.jsx (React island)import { cartCount } from '../stores/cart.js';export default function AddToCart() { return <button onClick={() => cartCount.set(cartCount.get() + 1)}>Add</button>;}// CartBadge.jsx (another React island, elsewhere on the page)import { useStore } from '@nanostores/react';import { cartCount } from '../stores/cart.js';export default function CartBadge() { const count = useStore(cartCount); // re-renders when the store changes return <span>Cart: {count}</span>;}ตอนนี้กดปุ่ม “Add” ใน island หนึ่งจะอัปเดต badge ใน island ที่แยกกันคนละที่ — เพราะทั้งคู่ subscribe store เดียวกัน ไม่ได้ subscribe กันเอง
flowchart TB store["nanostore: cartCount"] add["AddToCart island — writes"] --> store store --> badge["CartBadge island — reads and re-renders"]
3. slot: ส่ง children ที่ render บน server เข้าไปใน island
หัวข้อที่มีชื่อว่า “3. slot: ส่ง children ที่ render บน server เข้าไปใน island”บางครั้งคุณไม่ได้ต้องแชร์ state — แต่ต้องเอา content ที่ render บน server ไปวางในเปลือกที่ interactive framework island รับ <slot /> ได้แบบเดียวกับ .astro component ดังนั้น island <Tabs> ที่ interactive จึงห่อ panel ที่ static และ render บน server ไว้ได้ เปลือกเป็น interactive ส่วน content ถูกส่งเป็น HTML โดยไม่มี island JS เพิ่ม
---import Accordion from '../components/Accordion.jsx';---<Accordion client:visible> <p>This paragraph is server-rendered HTML, passed into the island via its slot.</p></Accordion>เลือกจากสามอย่าง
หัวข้อที่มีชื่อว่า “เลือกจากสามอย่าง”- ข้อมูลรู้ตอน render ไม่มีอัปเดต runtime → props จาก server
- island ต้องอัปเดตกันตอน runtime → nanostore
- ต้องการ chrome ที่ interactive ครอบ content ที่ static → slot
คว้า store มาใช้เฉพาะตอนที่มีการประสานงาน runtime ระหว่าง island แยก ๆ จริง ๆ ความต้องการ “shared state” ส่วนใหญ่กลายเป็น props เริ่มต้นหรือ slot ซึ่งส่ง JavaScript น้อยกว่า