🛑 1. The Problem First: "โค้ดที่ทำงานได้... แต่ดูแลต่อลำบาก"
ลองนึกถึงวันที่คุณต้องไปแก้โค้ดที่เพื่อนเขียนไว้เมื่อ 1 ปีที่แล้ว:
// ❌ Naive Approach: เขียนตามใจปาก ไม่สนสถาปัตยกรรม
useEffect(() => {
setupAnalytics();
checkAuth();
}, []); // 🌋 พัง! ใน React 18+ Dev Mode โค้ดนี้จะรัน 2 รอบ
# ทำให้ analytics ของคุณเบิ้ล 2 ครั้ง หรือการใช้ .sort() ตรงๆ ที่แอบไปเปลี่ยนข้อมูล
# ของ Component อื่นจนพังระเนระนาดโดยที่คุณหาจุดเกิดเหตุไม่เจอ
ปัญหา: การเขียน React ให้ "รันได้" นั้นใครก็ทำได้ แต่การเขียนให้ "ขยายต่อได้" (Scalable) โดยไม่เกิดผลข้างเคียง (Side Effects) ที่คาดเดาไม่ได้ คือสิ่งที่แยกวิศวกรซอฟต์แวร์ออกจากมือสมัครเล่นครับ
💡 2. Real-Life Analogy: การวางระบบห้องครัวในร้านอาหาร
- Initialization Guard: เหมือน "ระบบตอกบัตรพนักงาน". ไม่ว่าพนักงานจะเดินเข้าห้างมาเพื่อหาความเร็ว (Re-render) กี่รอบ แต่เขาห้ามตอกบัตรเข้างานซ้ำซ้อน งานที่ทำครั้งเดียวต้องจบที่ครั้งเดียวเท่านั้น
- Immutability (.toSorted): เหมือน "การถ่ายเอกสารก่อนขีดเขียน". แทนที่คุณจะแก้สูตรอาหารในสมุดเล่มหลักของร้าน คุณถ่ายเอกสารออกมาแผ่นหนึ่งแล้วค่อยขีดแก้ (Sort) บนนั้น เพื่อให้สมุดต้นฉบับยังคงความถูกต้องสำหรับเชฟคนอื่นเสมอ
- Stable Handlers (Ref Pattern): เหมือน "การใช้เบอร์โทรศัพท์ร้านเดิม แม้จะเปลี่ยนผู้จัดการ". แขก (Effect) จำแค่เบอร์โทรเดียว แต่เบอร์นั้นจะต่อสายไปหาผู้จัดการคนปัจจุบันเสมอโดยไม่ต้องแจ้งแขกทุุกครั้งที่เปลี่ยนคน
🚀 3. Execution Journey: มหากาพย์การสร้างโค้ดที่ยั่งยืน
Senior จะใช้ Pattern ที่เน้นความ "นิ่ง" และ "คาดเดาได้" (Predictability)
🛠 Step-by-step:
- The Module Guard: ใช้ตัวแปรนอก Component เพื่อคุมงานที่ต้องรันครั้งเดียวทั้้งแอป
- The Immutable Rule: เลิกใช้
.sort()หรือ.reverse()ที่ Mutate ข้อมูลเดิม และย้ายไปใช้.toSorted()แทน - The Event Ref: ใช้
useRefเพื่อเก็บฟังก์ชันล่าสุดไว้ให้useEffectเรียกใช้ โดยไม่ต้องใส่ใน Dependency Array (กัน Effect รันมั่ว) - The Cache Wrapper: ห่อหุ้มการเข้าถึง
localStorageที่ช้าด้วย Memory Cache (เช่น Map)
// ✅ Best Practice: Stable Handler Pattern (กัน Effect รันซ้ำซ้อน)
function SearchBox({ onSearch }) {
const searchRef = useRef(onSearch);
searchRef.current = onSearch; // 🛠 อัปเดตให้เป็นตัวล่าสุดเสมอ
useEffect(() => {
const handleKeydown = (e) => {
if (e.key === "Enter") searchRef.current(); // 🛠 เรียกผ่าน Ref ไม่ต้องใส่ onSearch ใน []
};
window.addEventListener("keydown", handleKeydown);
return () => window.removeEventListener("keydown", handleKeydown);
}, []); // 🛠 ว่างเปล่า! ไม่ต้องรันซ้ำทุุกครั้งที่ props เปลี่ยน
}
🪤 4. The Junior Trap: โรค "Dependency Array งอก"
จูเนียร์มักจะใส่ทุุกอย่างที่เครื่องมือ Linter เตือนลงไปใน Dependency Array ของ useEffect:
// ❌ Junior Trap: ใส่ทุุกอย่างจน Effect รันไม่หยุด
useEffect(() => {
doSomething(props.data, someCallback);
}, [props.data, someCallback]);
// 🌋 พัง! ถ้า parent ส่ง someCallback มาเป็น inline function (สร้างใหม่ทุุกรอบ)
# Effect ของคุณจะรันไม่หยุดทุุกครั้งที่มีการ Re-render จนเครื่องค้างไปเลย
ระวัง: Dependency Array ยิ่งยาวยิ่งอันตราย
✅ การแก้ไข: จงใช้ useCallback หุ้มฟังก์ชันที่ส่งมา หรือใช้ Event Ref Pattern เพื่อลดความซับซ้อนของ Dependency ครับ
⚖️ 5. The Why Matrix: Naive Code vs Senior Architecture
| หัวข้อ | Naive Code (จูเนียร์) | Senior Architecture |
|---|---|---|
| ความคาดเดาได้ | ต่ำ (มี Side Effects เยอะ) | 🚀 สูงสุด (ทุุกอย่างถูกควบคุม) |
| การ Refactor | ยาก (ขยับจุดหนึ่งพังทั้งแผง) | ⚡⚡ ง่าย (Component เป็นอิสระจากกัน) |
| ประสิทธิภาพ | ปานกลาง | 🚀 ดีที่สุด (ลด Re-render ที่ไม่จำเป็น) |
| ความรู้สึกทีม | ปวดหัวเมื่อต้องอ่าน | 😍 รักเลย (อ่านง่าย สวยงาม) |
🎓 6. Senior Mindset Summary
การเป็น Senior คือการมองว่า "เราเขียนโค้ดเพื่อการสื่อสาร ไม่ใช่เพื่อการสั่งงานคอมพิวเตอร์เพียงอย่างเดียว". โค้ดที่นิ่งและคาดเดาได้คือของขวัญที่ดีที่สุดที่คุณจะมอบให้กับเพื่อนร่วมทีมและตัวคุณเองในอีก 6 เดือนข้างหน้าครับ!