เจาะลึก React useEvent Hook ทางออกปัญหา Stale Closures แบบไม่ต้องปวดหัวกับ Dependency Array
แก้ปัญหาคลาสสิกของ React ด้วย useEvent จาก @reactuses/core ที่ให้ฟังก์ชันคงที่ตลอดอายุขัยแต่เข้าถึงสถานะล่าสุดได้เสมอ

ภาพประกอบจากคลังภาพสต็อก ไม่ใช่ภาพจากเหตุการณ์จริง
- นักพัฒนา React มักต้องเลือกระหว่างสร้างฟังก์ชันใหม่ทุกเรนเดอร์หรือใช้ useCallback ที่พ่วงปัญหาพึ่งพา Dependency Array
- useEvent ให้ฟังก์ชันที่ตัวตนไม่เคยเปลี่ยนแต่ภายในอ่านค่า State และ Props ล่าสุดได้เสมอ
- เบื้องหลังใช้เทคนิค Ref และ Layout Effect เพื่อผูกค่าล่าสุดไว้โดยไม่ทำลายกฎ Concurrent Rendering
- เหมาะสำหรับ Event Handlers, WebSockets และไลบรารีภายนอกที่ไม่ต้องการให้รีเซ็ตการทำงานโดยไม่จำเป็น
นักพัฒนา React ทุกคนย่อมต้องเคยเผชิญกับจุดตัดสำคัญเมื่อเขียนฟังก์ชัน Event Handler ที่ต้องอ่านค่า State โดยมีทางเลือกสองทางคือ ปล่อยเป็นฟังก์ชันธรรมดาแล้วยอมให้ทุกเรนเดอร์สร้าง Reference ใหม่จนทำลาย React.memo และทำให้ Effect ทำงานซ้ำซ้อน หรือห่อหุ้มด้วย useCallback แล้วเริ่มเล่นเกมไล่ล่าจับผิด Dependency ที่หากลืมใส่ตัวแปรใดตัวแปรหนึ่ง ฟังก์ชันก็จะมองเห็น State จากสามเรนเดอร์ก่อนหน้าทันที
ข้อผิดพลาดแบบหลังนี้มีชื่อเรียกว่า Stale Closure ซึ่งนับเป็นบั๊กที่พบได้บ่อยที่สุดในโค้ด React ระดับ Production แนวทางแก้ไขที่ถูกเสนอขึ้นครั้งแรกใน React RFC ปี 2022 และเปิดให้ใช้งานได้จริงผ่านไลบรารี @reactuses/core ในชื่อ useEvent ซึ่งช่วยสร้างฟังก์ชันที่ตัวตนไม่เปลี่ยนแปลงตลอดวงจรชีวิตของคอมโพเนนต์ แต่เนื้อหาภายในสามารถมองเห็น State และ Props ล่าสุดได้ตลอดเวลาโดยไม่ต้องเลือกแลกอย่างใดอย่างหนึ่ง
การทำความเข้าใจเรื่อง Stale Closure ถือเป็นก้าวสำคัญสำหรับนักพัฒนา React ระดับกลางถึงสูง เนื่องจากกลไกการทำงานของ Closure ใน JavaScript มักจับค่าตัวแปร ณ ขณะที่ฟังก์ชันถูกสร้างขึ้น การใช้ Hook เข้ามาช่วยจัดการ Reference ผ่าน Mutable Ref จึงเป็นรูปแบบการออกแบบที่ชาญฉลาดในการแยกแยะระหว่างโครงสร้างภายนอกที่ต้องนิ่งกับข้อมูลภายในที่ต้องเคลื่อนไหวตามเวลาจริง
โครงสร้างเบื้องหลังของ Hook ตัวนี้สั้นกระชับและชาญฉลาดโดยประกอบด้วยส่วนสำคัญดังนี้:
- ใช้ useRef เพื่อเก็บบันทึก Closure ล่าสุดไว้ใน handlerRef
- อัปเดตค่าผ่าน Layout Effect เพื่อให้มั่นใจว่าข้อมูลมีความสดใหม่ก่อนที่เบราว์เซอร์จะทำการ Render
- ส่งคืน Wrapper ที่ถูก Memoize ไว้ด้วย Dependency Array เปล่า ทำให้ตัวตนของฟังก์ชันไม่เคยเปลี่ยน

ภาพประกอบจากคลังภาพสต็อก ไม่ใช่ภาพจากเหตุการณ์จริง
การอัปเดต Ref ผ่าน useIsomorphicLayoutEffect แบบซิงโครนัสหลังจากการกลายพันธุ์ของ DOM ช่วยป้องกันไม่ให้ช่องว่างระหว่างเรนเดอร์เรียกใช้ Closure ของรอบก่อนหน้า โดยมีความปลอดภัยสำหรับ SSR ผ่านการสลับไปใช้ useEffect บนฝั่งเซิร์ฟเวอร์โดยอัตโนมัติเพื่อป้องกันคำเตือนเรื่อง Hydration
"Stable shell, fresh core."
Dev.to Article
ที่มา: Dev.to
พบข้อมูลผิดพลาดในบทความนี้? แจ้งปัญหาบทความนี้
ความคิดเห็น
แสดงความคิดเห็น