🛑 1. The Problem First: "ทำไมค่าใน Log ถึงไม่อัปเดต?"
ลองนึกถึงวันที่คุณสร้างปุ่มนับเลขง่ายๆ แล้วอยากจะ Log ค่าออกมาดูหลังจากผ่านไป 3 วินาที:
// ❌ Naive Approach: กับดัก Stale Closure
function Counter() {
const [count, setCount] = useState(0);
const handleLog = () => {
setTimeout(() => {
console.log("Count ปัจจุบันคือ:", count);
}, 3000);
};
return (
<button onClick={() => { setCount(count + 1); handleLog(); }}>
คลิกเพื่อเพิ่มและ Log
</button>
);
}
// 🌋 พัง! ต่อให้คุณรัวคลิกจนเลขไปถึง 10 แต่ใน Console จะโชว์เลข 0
# เสมอ เพราะฟังก์ชัน 'จำ' ค่า count ณ วินาทีที่มันถูกสร้าง (Closure)
# นี่คือสาเหตุที่โปรเจกต์ใหญ่ๆ มักจะมีบั๊กที่หาตัวยากครับ
ปัญหา: Hooks พึ่งพาความเข้าใจเรื่อง Scope และ Memory Management อย่างสูง การไม่เข้าใจว่า React 'จำ' ของทุุกอย่างไว้ใน Array ลับๆ จะทำให้คุณเจอพฤติกรรมแปลกๆ ที่แก้ยังไงก็ไม่หายครับ
💡 2. Real-Life Analogy: ลิ้นชักเก็บของในสำนักงาน
- Rules of Hooks: เหมือน "ลำดับของลิ้นชัก". ลิ้นชักที่ 1 เก็บชื่อ, ลิ้นชักที่ 2 เก็บอายุ ถ้าวันหนึ่งคุณเดินเข้าไปแล้วบอกว่า "วันนี้ไม่ต้องเอาลิ้นชักที่ 1 ออกมา" พนักงานจะหยิบลิ้นชักถัดไปมาให้คุณแทน ผลคือคุณจะเอาอายุมาใส่ในช่องชื่อ (Data Mismatch) และทุุกอย่างจะพังพินาศ
- Stale Closures: เหมือน "รูปถ่ายของข้อมูล". เมื่อคุณสั่ง
setTimeoutมันเหมือนคุณถ่ายรูปกระดานดำเก็บไว้ แม้ว่าเชฟจะเดินมาแก้เลขบนกระดานทุุกวินาที แต่ในรูปถ่ายที่คุณถืออยู่ (Closure) เลขยังเป็นตัวเดิมเสมอ - useRef: เหมือน "กล่องใสที่ทุุกคนเห็นค่าข้างในตลอดเวลา". ไม่ว่าเวลาจะผ่านไปนานแค่ไหน ทุุกคนจะเห็นสิ่งที่อยู่ในกล่องนั้นเป็นค่าล่าสุดเสมอ
🚀 3. Execution Journey: มหากาพย์การคุม Hooks ให้อยู่หมัด
Senior จะเขียน Hooks โดยคำนึงถึง "ความสดใหม่" ของข้อมูลและ "ความประหยัด" ของทรัพยากร
🛠 Step-by-step:
- The Order Guard: ห้ามใส่ Hooks ใน
ifหรือloopเด็ดขาด เพื่อรักษาลำดับของ Linked List ภายใน React - The Functional Update: ใช้ท่า
setCount(prev => prev + 1)เพื่อให้แน่ใจว่าเราได้ค่าล่าสุดเสมอโดยไม่ต้องพึ่งพิง Closure ภายนอก - The Ref Bridge: ใช้
useRefเมื่อต้องการเข้าถึงค่าปัจจุบันภายใน Effect หรือ Timeout ที่มีระยะเวลานาน - The Strategic Memo: ใช้
useMemoเฉพาะเมื่อมีการคำนวณที่หนักจริงๆ (เช่น Filter ข้อมูลหลักพัน) ไม่ใช่ใส่ทุุกจุดจน App หนักกว่าเดิม
// ✅ Best Practice: การแก้ปัญหา Stale Closure ด้วย Ref
function StableCounter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
countRef.current = count; // 🛠 อัปเดต 'ความจริงล่าสุด' เสมอ
const handleLog = () => {
setTimeout(() => {
console.log("ค่าที่แท้จริงตอนนี้คือ:", countRef.current); // 🛠 ได้ค่าล่าสุดแน่นอน
}, 3000);
};
// ...
}
🪤 4. The Junior Trap: โรค "Optimization Overload"
จูเนียร์มักจะกลัว Re-render จนใส่ useCallback ครอบทุุกฟังก์ชัน:
// ❌ Junior Trap: ใส่ useCallback กับงานกระจอก
const handleClick = useCallback(() => {
setOpen(false);
}, []);
// 🌋 พัง! เพราะการทำ useCallback มีค่าใช้จ่าย (CPU/Memory)
# ในการเปรียบเทียบ Dependency Array และจองพื้นที่หน่วยความจำ
# ซึ่งแพงกว่าการปล่อยให้ฟังก์ชันถูกสร้างใหม่เฉยๆ เสียอีก
ระวัง: ทุุกครั้งที่ใช้ Hooks เพื่อ Optimize คุณกำลังแลก 'ความเร็ว' กับ 'หน่วยความจำ'
✅ การแก้ไข: จงใช้ useCallback ก็ต่อเมื่อคุณต้องส่งฟังก์ชันนั้นไปเป็น Props ให้ลูกที่ทำ React.memo ไว้เท่านั้นครับ
⚖️ 5. The Why Matrix: useEffect vs useLayoutEffect
| หัวข้อ | useEffect (ทั่วไป) | useLayoutEffect (ฉุกเฉิน) |
|---|---|---|
| จังหวะการรัน | รันหลังจากวาดหน้าจอเสร็จ | 🚨 รันก่อน Browser จะวาดหน้าจอ |
| UX | อาจเห็นภาพกระตุก (Flicker) | 🚀 ลื่นไหล (ไม่มีรอยต่อ) |
| Performance | ไม่บล็อกการวาดหน้าจอ | 🐢 บล็อกการวาด (เว็บอาจอืด) |
| เหมาะกับ | Fetch ข้อมูล, ติดตาม Log | คำนวณขนาด (Bounding Box) ของ UI |
🎓 6. Senior Mindset Summary
การเป็น Senior คือการมองว่า "Hooks คือเครื่องมือในการจัดการสถานะ ไม่ใช่ของเล่นสำหรับปรับแต่งความเร็วไปวันๆ". โค้ดที่เรียบง่ายและทำงานถูกต้อง สำคัญกว่าโค้ดที่ดูฉลาดแต่แฝงไปด้วยบั๊กที่มองไม่เห็นครับ!