ทำความรู้จักกับ useReducer เมื่อ State เริ่มคุมไม่อยู่
เวลาเราเขียนโปรแกรมด้วย React ช่วงแรกเรามักใช้ useState (คำสั่งเก็บข้อมูลที่เปลี่ยนแปลงได้) เพื่อจัดการข้อมูลในหน้าจอ แต่วันหนึ่งเมื่อโปรเจกต์เริ่มใหญ่ขึ้น ข้อมูลของเราอาจมีหลายส่วนที่เกี่ยวข้องกันจนจัดการยาก ลองนึกภาพว่าคุณกำลังจัดการคลังสินค้า ถ้าคุณจดบันทึกของเข้าออกด้วยกระดาษใบเดียวพอของเริ่มเยอะ คุณจะเริ่มสับสนและจดผิดจดถูกได้ง่าย การใช้ useReducer (เครื่องมือจัดการข้อมูลที่ซับซ้อน) ก็เหมือนกับการเปลี่ยนมาใช้ "ระบบลงทะเบียน" ที่มีกฎเกณฑ์ชัดเจน แทนที่จะเปลี่ยนค่าข้อมูลตรงๆ เราจะส่ง "คำสั่ง" ไปให้ระบบจัดการแทน วิธีนี้ช่วยให้โค้ดของเราเป็นระเบียบและเดาทางได้ง่ายขึ้นมาก โดยเฉพาะเวลาที่ข้อมูลต้องเปลี่ยนไปตามเงื่อนไขหลายอย่างพร้อมกัน การเขียนโปรแกรม ที่ดีจึงต้องรู้จักเลือกใช้เครื่องมือให้เหมาะกับขนาดของงาน
Reducer Pattern คืออะไรและทำไมต้องใช้
Reducer Pattern (รูปแบบการเขียนโปรแกรมเพื่อเปลี่ยนข้อมูลตามเงื่อนไข) คือแนวคิดที่คุณแยกส่วนการคำนวณข้อมูลออกมาจากส่วนการแสดงผล วิธีนี้ช่วยให้คุณไม่ต้องเขียนโค้ดเปลี่ยนค่าตัวแปรยาวเหยียดไว้ในหน้าเว็บ เปรียบเทียบง่ายๆ เหมือนการสั่งอาหารในร้านอาหาร คุณไม่ได้เดินไปทำอาหารเองในครัว แต่คุณส่ง "รายการสั่ง" (Action) ไปให้พ่อครัว พ่อครัวจะเป็นคนตัดสินใจเองว่าจะทำเมนูไหนตามขั้นตอนที่เตรียมไว้ (Reducer) วิธีนี้ทำให้คุณไม่ต้องกังวลเรื่องรายละเอียดในครัว ถ้าคุณเป็นมือใหม่ที่อยากเก่งขึ้น การเข้าใจเรื่องนี้จะช่วยให้คุณทำงานร่วมกับคนอื่นในทีมได้ง่ายขึ้น เพราะโค้ดของคุณจะมีโครงสร้างที่ชัดเจน ใครมาอ่านต่อก็เข้าใจได้ทันทีว่า "ข้อมูลนี้เปลี่ยนไปเพราะคำสั่งอะไร" ไม่ใช่การเดาสุ่มจากการไล่เช็กโค้ดทีละบรรทัด
เตรียมตัวเขียนโค้ดด้วย State ที่ซับซ้อน
ก่อนจะเขียน useReducer เราต้องเตรียมตัวแปรที่เรียกว่า initialState (ค่าเริ่มต้นของข้อมูล) และ reducer (ฟังก์ชันตัวตัดสินใจว่าจะเปลี่ยนข้อมูลอย่างไร) ให้พร้อมเสียก่อน ขั้นตอนการเตรียมโค้ดมีดังนี้:
- กำหนด
initialStateให้เป็น Object (กลุ่มข้อมูลที่มีชื่อเรียก) เพื่อเก็บค่าหลายตัวพร้อมกัน - สร้างฟังก์ชัน
reducerที่รับค่าstate(ข้อมูลปัจจุบัน) และaction(สิ่งที่ต้องการให้ทำ) - ใช้คำสั่ง
switch(โครงสร้างเช็กเงื่อนไข) เพื่อแยกการทำงานของแต่ละคำสั่ง
ใน reducer เราจะคืนค่า state ใหม่กลับไปเสมอ ห้ามไปแก้ไขค่าเดิมตรงๆ เด็ดขาด เพราะ React จะไม่รู้ว่าข้อมูลเปลี่ยนไปแล้วถ้าคุณไม่สร้างค่าใหม่ขึ้นมาแทนที่ การยึดหลักการนี้จะทำให้แอปพลิเคชันของคุณทำงานได้ถูกต้องและไม่เกิดบั๊ก (ข้อผิดพลาดของโปรแกรม) ที่หาสาเหตุยาก
// กำหนดค่าเริ่มต้น
const initialState = { count: 0, loading: false };
// ฟังก์ชันตัดสินใจเปลี่ยนข้อมูล
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
default:
return state;
}
}
ใช้งานจริงด้วย Dispatch และ Action
เมื่อเรามี reducer แล้ว ขั้นตอนต่อไปคือการนำมาใช้งานจริงในหน้าเว็บผ่านคำสั่ง useReducer ซึ่งจะคืนค่าตัวแปร state และฟังก์ชัน dispatch (ตัวส่งคำสั่ง) ออกมาให้เราใช้ dispatch ทำหน้าที่เหมือนพนักงานรับออเดอร์ที่คอยส่ง "คำสั่ง" (Action) ไปให้ reducer จัดการ การส่งคำสั่งมักจะทำในรูปแบบ Object ที่มีชื่อว่า type (ประเภทคำสั่ง) เพื่อให้ฟังก์ชันของเราแยกออกว่าต้องทำอะไร มือใหม่หลายคนมักพลาดโดยการลืมใส่ type ในคำสั่ง ทำให้ระบบทำงานไม่ถูก วิธีแก้คือให้ตรวจสอบว่าค่าที่ส่งเข้าไปใน dispatch มีรูปแบบตรงกับที่ reducer รอรับอยู่เสมอ การทำแบบนี้จะทำให้ โปรแกรมเมอร์ มืออาชีพอย่างคุณทำงานได้แม่นยำขึ้น
import { useReducer } from 'react';
function Counter() {
// เรียกใช้ reducer
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>ตัวเลขคือ: {state.count}</p>
{/* ส่งคำสั่งเพิ่มค่า */}
<button onClick={() => dispatch({ type: 'increment' })}>เพิ่ม</button>
</div>
);
}
ข้อควรระวังเมื่อใช้ useReducer
จุดที่คนส่วนใหญ่มักพลาดคือการใส่ "งานที่ซับซ้อน" เกินไปไว้ใน reducer เช่น การดึงข้อมูลจากอินเทอร์เน็ต (API) หรือการสุ่มตัวเลข reducer ควรเป็นฟังก์ชันที่ทำหน้าที่คำนวณค่าเท่านั้น ถ้าคุณใส่คำสั่งที่คาดเดาผลลัพธ์ไม่ได้ไว้ใน reducer มันจะทำให้แอปของคุณรวนและเทสยากขึ้นมาก ให้จำไว้ว่า Reducer ต้องเป็นฟังก์ชันที่สะอาด คือรับค่าเดิม ได้ผลลัพธ์เดิมเสมอ ไม่ว่าคุณจะเรียกใช้กี่ครั้งก็ตาม หากคุณต้องดึงข้อมูลจากภายนอก ให้จัดการที่ส่วนอื่นของโปรแกรมก่อน แล้วค่อยส่งผลลัพธ์ที่ได้เข้า dispatch อีกที การแยกส่วนงานให้ชัดเจนเป็นหัวใจสำคัญของการเป็น โปรแกรมเมอร์ ที่มีวินัยและเขียนโค้ดได้มาตรฐานสากล
สรุป: การนำไปใช้ในโปรเจกต์จริง
การใช้ useReducer ไม่ใช่แค่เรื่องของเทคนิค แต่เป็นเรื่องของการวางโครงสร้างความคิดให้เป็นระเบียบ เมื่อคุณเริ่มทำโปรเจกต์ที่ต้องมีข้อมูลซับซ้อน เช่น ระบบตะกร้าสินค้า หรือหน้าฟอร์มกรอกข้อมูลยาวๆ ให้ลองนึกถึง useReducer เป็นอันดับแรก ตัวอย่างการนำไปใช้: สมมติคุณทำโปรเจกต์ "ระบบจัดการรายการสิ่งที่ต้องทำ" (To-Do List) แทนที่จะใช้ useState แยกเก็บหลายตัว คุณสามารถสร้าง reducer ที่จัดการได้ทั้ง "เพิ่มรายการ", "ลบรายการ" และ "ทำเครื่องหมายว่าเสร็จแล้ว" ไว้ในที่เดียว การฝึกเขียนโค้ดให้สะอาดตั้งแต่ต้น จะช่วยให้คุณประหยัดเวลาในการแก้บั๊กในระยะยาว แม้ช่วงแรกจะรู้สึกว่าเขียนโค้ดยาวขึ้น แต่ผลลัพธ์ที่ได้คือโปรแกรมที่เสถียรและขยายต่อได้ง่ายกว่าเดิมมาก เริ่มฝึกวันนี้แล้วคุณจะก้าวเข้าใกล้คำว่า มืออาชีพ ไปอีกขั้นครับ