ข้ามไปยังเนื้อหา

External Stores

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>;
}

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)"]
useSyncExternalStore ทำให้ React สอดคล้องกับแหล่งข้อมูลภายนอก
ความต้องการใช้
ใช้โดย component เดียวLocal useState / useReducer
แชร์โดย component ใกล้ ๆ ไม่กี่ตัวlift state ไปที่ common ancestor
แชร์กว้าง เปลี่ยนไม่บ่อยContext (+ useReducer)
เข้าถึงกระจัดกระจาย, ต้อง fine-grained subscription หรืออยู่นอก ReactExternal store (ผ่าน useSyncExternalStore)

สรุปตามจริง: ไปหา store เมื่อคุณ รู้สึกถึงความเจ็บปวด ไม่ใช่ล่วงหน้า หลายทีมใส่ Redux ตั้งแต่วันแรกและไม่เคยต้องใช้เลย เริ่มต่ำบนบันได แล้วปีนเมื่อแอปบอกให้ปีน

external store เป็นตัวเลือกที่ถูกต้องเมื่อไร?
"tearing" คืออะไร?
useSyncExternalStore ทำอะไร?
argument หลักสองตัวของ useSyncExternalStore คืออะไร?