เกิดอะไรขึ้น
ตอนทำหน้าหนึ่งใน development useEffect ของเรารันสองรอบตั้งแต่โหลดครั้งแรก เปิด subscription ซ้ำสองอัน และยิง request ซ้ำสองครั้ง ความคิดแรกของเราคือปิด Strict Mode เพื่อให้มันหยุด
แต่สุดท้ายพบว่า Strict Mode ไม่ได้ทำให้เกิดบั๊ก มันแค่ทำให้เราเห็นบั๊กที่มีอยู่แล้ว
ทำไมถึงเป็นแบบนั้น
ใน development ตั้งแต่ React 18 Strict Mode จะจำลองว่า component ถูก unmount แล้ว mount ใหม่ คือรัน effect, รัน cleanup, แล้วรัน effect อีกรอบ โดย state ยังอยู่เหมือนเดิม และเกิดเฉพาะตอน development ไม่มีใน production build
จุดประสงค์คือเช็กว่าทุก effect มี cleanup ที่ย้อนสิ่งที่ setup ไว้ได้ครบ effect ที่เขียนถูกต้องจะทำงานเหมือนกันไม่ว่ารันรอบเดียวหรือ setup, cleanup, setup ถ้าเกิด subscription ซ้ำ แปลว่า cleanup ขาดหรือไม่ครบ และบั๊กเดียวกันจะโผล่ใน production ตอน component ถูก remount จริง เช่นผู้ใช้เปลี่ยนหน้าไปแล้วกลับมา
ตอนนี้เราทำอย่างไร
เขียน cleanup ให้ย้อนสิ่งที่ effect ทำให้ครบ เช่น unsubscribe, เคลียร์ timer, ปิด connection, ยกเลิก fetch สำหรับการดึงข้อมูล ให้ใช้ AbortController หรือ flag สำหรับข้าม response ที่หมดอายุ request ซ้ำใน dev ก็จะไม่มีผลเสีย และ library อย่าง React Query หรือ SWR ก็ช่วย dedupe ให้ได้
ส่วนงานที่ควรเกิดครั้งเดียวต่อหนึ่งการกระทำของผู้ใช้ เช่นส่งออเดอร์หรือส่ง event การซื้อ ไม่ควรอยู่ใน effect ให้ย้ายไปไว้ใน event handler
useEffect(() => {
const controller = new AbortController()
fetch('/api/items', { signal: controller.signal })
.then(res => res.json())
.then(setItems)
.catch(err => {
if (err.name !== 'AbortError') console.error(err)
})
return () => controller.abort()
}, [])เช็กลิสต์สั้น ๆ
- 1อ่าน effect แล้วลิสต์ทุกอย่างที่มัน setup ไว้
- 2เขียน cleanup ย้อนทีละรายการ
- 3ยกเลิก fetch ที่ยังค้างอยู่ด้วย AbortController
- 4ย้ายงานที่เกิดจากการกระทำของผู้ใช้ไปไว้ใน event handler
- 5เปิด Strict Mode ทิ้งไว้
จำไว้สั้น ๆ
ถ้า effect พังเมื่อรันซ้ำ ให้แก้ cleanup ไม่ใช่ปิด Strict Mode