เกิดอะไรขึ้น
ในโปรเจกต์หนึ่งของเรา ช่องค้นหาคุมตารางที่มี 5,000 แถว เราใส่ debounce ไว้ที่ input แล้ว ช่วงพิมพ์รู้สึกโอเค แต่พอหมดเวลาหน่วง หน้าเว็บจะค้างไปพักหนึ่งระหว่างที่ตาราง render ใหม่
ปัญหาคือ debounce ไม่ได้ทำให้งานหนักหายไป แค่ย้ายไปทำทีหลัง ทุกครั้งที่ timer ทำงาน ผู้ใช้ก็ยังโดนล็อกหน้าจนกว่า React จะ render เสร็จ
ทำไมถึงเป็นแบบนั้น
debounce หน่วงแค่จังหวะที่ state ถูกอัปเดต แต่พออัปเดตแล้ว งาน render ก็ใหญ่เท่าเดิม state update ปกติถือเป็นงานเร่งด่วน React จึงทำให้จบโดยไม่หยุดกลางทาง ตัวอักษรที่พิมพ์เข้ามาระหว่างนั้นต้องรออยู่ในคิว
useDeferredValue จะทำเครื่องหมายว่าค่านั้นมีความสำคัญต่ำกว่า React จะ render ด้วยค่าเก่าก่อน (เบา) แล้วค่อยเริ่ม render ด้วยค่าใหม่อยู่เบื้องหลัง ถ้ามีการพิมพ์ตัวใหม่เข้ามา React จะทิ้ง render ที่ทำค้างไว้แล้วเริ่มใหม่ด้วยค่าล่าสุด ช่องพิมพ์เลยตอบสนองทันตลอด
ตอนนี้เราทำอย่างไร
ตอนนี้เรา bind input กับ state ปกติ แล้วส่ง useDeferredValue(query) ให้ list เท่านั้น และห่อ list ด้วย memo เพราะถ้าไม่ห่อ component ลูกจะ render ซ้ำด้วยค่าเก่าอยู่ดี ก็ไม่ได้อะไรเลย เรายังเทียบ query กับ deferredQuery เพื่อทำให้ list จางลงระหว่างที่ยังตามไม่ทันได้ด้วย
แต่ debounce ยังเป็นเครื่องมือที่ถูกต้องเมื่อทุกการอัปเดตต้องยิง request ไป network เพราะ useDeferredValue ไม่ได้ลดจำนวน request ถ้าปัญหาคือต้นทุนการ render ฝั่ง client ล้วน ๆ มันช่วยตัดเวลารอที่ต้องเดาเองออกไปได้
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const isStale = query !== deferredQuery
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.6 : 1 }}>
<HeavyList query={deferredQuery} />
</div>
</>
)
// HeavyList ต้องห่อด้วย memo
const HeavyList = memo(function HeavyList({ query }: { query: string }) {
// render rows ที่ filter ด้วย query
return null
})เช็กลิสต์สั้น ๆ
- 1เก็บค่าใน input ไว้ใน state ปกติ เพื่อให้การพิมพ์ไม่ถูกหน่วง
- 2สร้าง deferredQuery ด้วย useDeferredValue(query)
- 3ส่ง deferredQuery ให้ list หนัก ๆ และห่อ list ด้วย memo
- 4แสดงสถานะจางลงเมื่อ query ยังไม่เท่ากับ deferredQuery
จำไว้สั้น ๆ
ถ้าอยากให้ render หนัก ๆ ถูกขัดจังหวะได้ ให้ใช้ useDeferredValue ส่วน debounce เก็บไว้ใช้ตอนต้องลดจำนวน request