External Stores
เมื่อไรควรออกจาก built-in state ของ React
หัวข้อที่มีชื่อว่า “เมื่อไรควรออกจาก built-in state ของ React”useState, useReducer และ Context รับมือแอปส่วนใหญ่ได้ คุณจะไปหา external store (Zustand, Jotai, Redux Toolkit, XState) เมื่อชนขีดจำกัดของเครื่องมือพวกนี้:
- state ถูกอ่าน/เขียนโดย หลาย component ในที่กระจัดกระจาย และพฤติกรรม “re-render ทุก consumer” ของ Context หยาบเกินไป
- คุณอยากได้ fine-grained subscription — component ที่ re-render เฉพาะเมื่อ slice ของตัวเอง ใน store เปลี่ยน ไม่ใช่ทุกครั้งที่ store update
- state มี ตรรกะซับซ้อนหรืออยู่นอก React tree (cache, websocket connection, state machine) ที่ควรมีอยู่อิสระจาก component ใด ๆ
external store เก็บ state ไว้ นอก React และให้ component subscribe เฉพาะส่วนที่ใช้
// Zustand — the store lives outside React; components select a slice.import { create } from "zustand";
const useCartStore = create((set) => ({ items: [], add: (item) => set((s) => ({ items: [...s.items, item] })),}));
function CartCount() { // Re-renders only when items.length changes — a fine-grained subscription. const count = useCartStore((s) => s.items.length); return <span>{count}</span>;}useSyncExternalStore: subscribe อย่างปลอดภัย
หัวข้อที่มีชื่อว่า “useSyncExternalStore: subscribe อย่างปลอดภัย”store ป้อนข้อมูลเข้า React อย่างปลอดภัยได้อย่างไร? แต่ก่อนคุณจะต่อ useEffect + useState แต่นั่นมีบั๊กแฝงภายใต้ concurrent rendering: React render ในเบื้องหลังได้ และค่าภายนอกอาจเปลี่ยน กลาง render ทำให้ component ต่าง ๆ ใน render เดียวกันอ่านค่าคนละค่า ความไม่สอดคล้องนี้เรียกว่า tearing — บางส่วนของ UI แสดงค่าเก่า บางส่วนแสดงค่าใหม่
React ให้ useSyncExternalStore มาเพื่อ subscribe external store ใด ๆ โดยไม่ tearing library ของ store ใช้ hook ตัวนี้อยู่เบื้องหลัง แต่คุณใช้ตรง ๆ ก็ได้:
import { useSyncExternalStore } from "react";
// subscribe: register a callback, return an unsubscribe fn.// getSnapshot: return the current value (must be consistent within a render).function useOnlineStatus() { return useSyncExternalStore( (callback) => { window.addEventListener("online", callback); window.addEventListener("offline", callback); return () => { window.removeEventListener("online", callback); window.removeEventListener("offline", callback); }; }, () => navigator.onLine, // client snapshot () => true // server snapshot (for SSR) );}flowchart LR store["External store (นอก React)"] -->|getSnapshot| hook["useSyncExternalStore"] store -->|subscribe/notify| hook hook --> react["React อ่าน snapshot ที่สอดคล้องหนึ่งเดียวต่อ render (ไม่ tearing)"]
เลือกบ้านให้ state
หัวข้อที่มีชื่อว่า “เลือกบ้านให้ state”| ความต้องการ | ใช้ |
|---|---|
| ใช้โดย component เดียว | Local useState / useReducer |
| แชร์โดย component ใกล้ ๆ ไม่กี่ตัว | lift state ไปที่ common ancestor |
| แชร์กว้าง เปลี่ยนไม่บ่อย | Context (+ useReducer) |
| เข้าถึงกระจัดกระจาย, ต้อง fine-grained subscription หรืออยู่นอก React | External store (ผ่าน useSyncExternalStore) |
สรุปตามจริง: ไปหา store เมื่อคุณ รู้สึกถึงความเจ็บปวด ไม่ใช่ล่วงหน้า หลายทีมใส่ Redux ตั้งแต่วันแรกและไม่เคยต้องใช้เลย เริ่มต่ำบนบันได แล้วปีนเมื่อแอปบอกให้ปีน