จัดโครงสร้าง state ที่ซับซ้อนด้วย reducer แบบ pure ที่รวมศูนย์การเปลี่ยนแปลงและตัดคอมบิเนชันที่เป็นไปไม่ได้ออกไป
เปิดบทเรียนนี้ใน Kodokonทันทีที่ component หนึ่งต้องประสาน state หลายค่าที่เปลี่ยนไปพร้อมกัน - สถานะของ request, ข้อมูลบางอย่าง, ข้อผิดพลาด - การเพิ่มจำนวนการเรียก useState จะทำให้ logic ของการเปลี่ยนสถานะกระจัดกระจายไปตาม event handler ทุกตัว คุณรู้อาการดี: มี setter สามตัวที่ต้องเรียกให้ถูกลำดับ และไม่ช้าก็เร็วก็จะเกิด state ที่ไม่สอดคล้องกัน เช่นสถานะ success ที่อยู่ข้างๆ error ที่ไม่ใช่ null useReducer พลิกความรับผิดชอบนี้: state กลายเป็น object เดียว และทุกการเปลี่ยนแปลงจะผ่าน action ที่ตีความโดยฟังก์ชันกลางเพียงตัวเดียวคือ reducer
function requestReducer(state, action) {
switch (action.type) {
case "start":
return { status: "loading", data: null, error: null };
case "resolve":
return {
status: "success",
data: action.data,
error: null,
};
case "reject":
return {
status: "error",
data: null,
error: action.error,
};
default:
throw new Error("Unknown action: " + action.type);
}
}reducer เป็นฟังก์ชันแบบ pure: มันรับ state ปัจจุบันกับ action แล้วคืน state ถัดไป โดยไม่มี side effect ประโยชน์เห็นได้ทันที: การเปลี่ยนสถานะ ชัดเจนและครบถ้วน, state ที่เป็นไปไม่ได้หายไปตั้งแต่ต้นทาง (case แต่ละตัวสร้าง object ที่สอดคล้องกันขึ้นใหม่) และ logic อ่านได้ที่จุดเดียวแทนที่จะกระจัดกระจายไปทั่ว สัญญาณเตือนที่มีประโยชน์: ถ้าคุณพบว่าตัวเองก็อปวางลำดับการเรียก setState ชุดเดิมในสองที่ คุณน่าจะกำลังขาด reducer อยู่
function UserProfile() {
const [state, dispatch] = useReducer(requestReducer, {
status: "idle",
data: null,
error: null,
});
async function load(id) {
dispatch({ type: "start" });
try {
const res = await fetch("/api/users/" + id);
const data = await res.json();
dispatch({ type: "resolve", data });
} catch (error) {
dispatch({ type: "reject", error });
}
}
return <button onClick={() => load(1)}>Load</button>;
}การเลือกระหว่าง useState กับ useReducer ไม่ใช่เรื่องของรสนิยม ใช้ useState ต่อไปสำหรับค่าที่เรียบง่ายและเป็นอิสระ; เปลี่ยนไปใช้ useReducer เมื่อ state ถัดไปขึ้นอยู่กับค่าก่อนหน้าหรือเมื่อหลาย field เปลี่ยนไปพร้อมกัน ข้อดีเพิ่มเติมอีกสองอย่าง: อาร์กิวเมนต์ตัวที่สามให้ การ initialize แบบ lazy (useReducer(reducer, arg, init) จะเรียก init เฉพาะตอน render ครั้งแรก) และ React รับประกันว่า dispatch จะคง identity เดิมจาก render หนึ่งไปยังอีก render หนึ่ง - คุณส่งมันลงไปให้ลูกได้โดยไม่ต้องใช้ useCallback