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

useReducer Patterns

useState เหมาะสุดกับ state ก้อนที่เป็นอิสระต่อกัน แต่เมื่อหลายค่าต้องเปลี่ยน พร้อมกัน เพื่อตอบสนอง event เดียวกัน — และ state ถัดไปขึ้นกับ state ปัจจุบันและ action — reducer จะรวมตรรกะนั้นไว้ใน pure function เดียว

// The reducer is a pure function: (state, action) => nextState.
function tasksReducer(state, action) {
switch (action.type) {
case "added":
return [...state, { id: action.id, text: action.text, done: false }];
case "toggled":
return state.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
case "deleted":
return state.filter((t) => t.id !== action.id);
default:
throw new Error("unknown action: " + action.type);
}
}
function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, []);
// You describe WHAT happened (an action); the reducer decides the next state.
return <button onClick={() => dispatch({ type: "added", id: 1, text: "Learn" })}>Add</button>;
}

useReducer คืน state ปัจจุบันและ function dispatch คุณไม่คำนวณ state ถัดไปที่จุดเรียก — คุณ dispatch action ที่อธิบายว่าเกิดอะไรขึ้น แล้ว reducer คำนวณผลลัพธ์ในที่เดียว

flowchart TB
  subgraph a["setState หลายตัว"]
    e1["event"] --> s1["setX"]
    e1 --> s2["setY"]
    e1 --> s3["setZ (ตรรกะกระจัดกระจาย)"]
  end
  subgraph b["reducer เดียว"]
    e2["event"] --> d["dispatch(action)"] --> r["reducer: ตรรกะ transition ทั้งหมดอยู่นี่"]
  end
setState กระจัดกระจาย vs reducer เดียว

ใช้ useReducer แทน useState เมื่อ:

  • State transition ซับซ้อน — หลาย field เปลี่ยนพร้อมกัน หรือค่าถัดไปขึ้นกับค่าก่อนหน้าแบบไม่ธรรมดา
  • อยากให้ตรรกะอยู่ในที่เดียวที่ test ได้ — reducer เป็น function ธรรมดาที่ unit-test ได้โดยไม่ต้องมี React เลย: reducer(state, action) เข้า, nextState ออก
  • action บอก intentdispatch({ type: "checkout_failed" }) อ่านง่ายกว่ากอง setter และให้ log ของ สิ่งที่เกิดขึ้น

reducer purity คือกฎเดียวกับ render: ให้ state และ action เดิม ต้องคืน next state เดิม โดย ไม่มี side effect และ ไม่ mutate (คืน object/array ใหม่)

รวมสองเครื่องมือจากโมดูลนี้ แล้วคุณจะได้ app-wide state management แบบเบา ๆ โดยไม่มี dependency เลย: reducer ถือตรรกะ และ Context ส่ง state กับ dispatch ลงไปตาม tree

const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null); // split: state vs dispatch
function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, []);
return (
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>{children}</TasksDispatchContext>
</TasksContext>
);
}
// Anywhere below: read state and dispatch actions.
function useTasks() { return useContext(TasksContext); }
function useTasksDispatch() { return useContext(TasksDispatchContext); }

การ split state กับ dispatch เป็นสอง context ทำให้ component ที่ dispatch อย่างเดียว (ไม่เคยอ่าน list) ไม่ re-render เมื่อ list เปลี่ยน — dispatch stable ตลอดอายุของ component pattern นี้ครอบคลุมเคส “ฉันต้องใช้ Redux” ได้มากอย่างน่าประหลาดโดยไม่ต้องใช้ library ใด ๆ

reducer คืออะไร?
ควรใช้ useReducer แทน useState เมื่อไร?
ใน pattern context + reducer ทำไมต้อง split state กับ dispatch เป็นสอง context?
reducer ต้อง NOT ทำอะไร?