useReducer Patterns
จากหลาย setState สู่ reducer เดียว
หัวข้อที่มีชื่อว่า “จากหลาย setState สู่ reducer เดียว”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 คำนวณผลลัพธ์ในที่เดียว
ทำไมควรใช้ reducer
หัวข้อที่มีชื่อว่า “ทำไมควรใช้ 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 ใช้ useReducer แทน useState เมื่อ:
- State transition ซับซ้อน — หลาย field เปลี่ยนพร้อมกัน หรือค่าถัดไปขึ้นกับค่าก่อนหน้าแบบไม่ธรรมดา
- อยากให้ตรรกะอยู่ในที่เดียวที่ test ได้ — reducer เป็น function ธรรมดาที่ unit-test ได้โดยไม่ต้องมี React เลย:
reducer(state, action)เข้า,nextStateออก - action บอก intent —
dispatch({ type: "checkout_failed" })อ่านง่ายกว่ากอง setter และให้ log ของ สิ่งที่เกิดขึ้น
reducer purity คือกฎเดียวกับ render: ให้ state และ action เดิม ต้องคืน next state เดิม โดย ไม่มี side effect และ ไม่ mutate (คืน object/array ใหม่)
Context + reducer: app state โดยไม่ต้องใช้ library
หัวข้อที่มีชื่อว่า “Context + reducer: app state โดยไม่ต้องใช้ library”รวมสองเครื่องมือจากโมดูลนี้ แล้วคุณจะได้ 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 ใด ๆ