Memoization in Practice
สามเครื่องมือ สามหน้าที่
หัวข้อที่มีชื่อว่า “สามเครื่องมือ สามหน้าที่”memoization คือการ cache ผลลัพธ์ระหว่าง render เพื่อให้ React ข้ามการคำนวณหรือ re-render ได้ มีสามเครื่องมือ และแต่ละตัวทำหน้าที่ต่างกัน:
useMemo(fn, deps)— cache ค่าที่ return ของการคำนวณที่หนัก คำนวณใหม่เฉพาะเมื่อ dependency เปลี่ยนuseCallback(fn, deps)— cache identity ของ function คืน reference เดิมข้าม render จนกว่า dependency จะเปลี่ยนReact.memo(Component)— ห่อ component ให้ข้าม re-render เมื่อ props shallow-equal
// useMemo: ข้ามการ sort list ใหญ่ใหม่ ยกเว้น list หรือ order เปลี่ยนconst sorted = useMemo(() => items.slice().sort(compare), [items]);
// useCallback: คง onSelect ให้เสถียร เพื่อไม่ให้ memoized child re-renderconst onSelect = useCallback((id) => setSelected(id), []);
// React.memo: ข้าม re-render เมื่อ props ไม่เปลี่ยนconst Row = React.memo(function Row({ item, onSelect }) { /* ... */ });ทำไม useCallback กับ React.memo เป็นคู่กัน
หัวข้อที่มีชื่อว่า “ทำไม useCallback กับ React.memo เป็นคู่กัน”React.memo เทียบ props ด้วย shallow equality (=== ต่อ prop) primitive เทียบด้วยค่า แต่ object, array และ function เทียบด้วย reference — และ component สร้างตัวใหม่เอี่ยมทุก render
flowchart TB parent["Parent re-render"] --> inline["สร้าง onClick ใหม่, object style ใหม่"] inline --> memo["React.memo(Child) เทียบ props"] memo --> fail["reference ใหม่ != reference เก่า → shallow-equal fail → Child re-render อยู่ดี"]
// ❌ function และ object ใหม่ทุก render — React.memo(Child) ข้ามไม่ได้เลย<Child onClick={() => doThing()} style={{ color: "red" }} />
// ✅ reference เสถียร — ตอนนี้ React.memo(Child) ข้ามได้จริงconst onClick = useCallback(() => doThing(), []);const style = useMemo(() => ({ color: "red" }), []);<Child onClick={onClick} style={style} />นี่คือหัวใจ: React.memo คุ้มก็ต่อเมื่อ ทุก prop เสถียรเชิง reference ซึ่งมักหมายถึงต้อง memoize props ที่เป็น object/array/function ด้วย useMemo/useCallback พลาด prop เดียว memo ก็ไม่ทำอะไรเลย
React Compiler เปลี่ยนสมการ
หัวข้อที่มีชื่อว่า “React Compiler เปลี่ยนสมการ”การคง reference ให้เสถียรด้วยมือน่าเบื่อและพลาดง่าย React Compiler (ตัว optimize ตอน build ของ React) วิเคราะห์ component ของคุณและ memoize ค่ากับ component ให้อัตโนมัติในจุดที่ปลอดภัย — เหมือนใส่ useMemo/useCallback/memo ให้ทั่วโดยที่คุณไม่ต้องเขียน
พอเปิด compiler บน codebase แล้ว แนวทางจะพลิก:
- อย่า เขียน
useMemo/useCallbackเองโดย default — compiler จัดการ common case ให้ - จง เขียน component ให้ pure และทำตาม Rules of Hooks — นั่นคือสิ่งที่ให้ compiler optimize ได้อย่างปลอดภัย
- สงวน manual memoization ไว้สำหรับกรณีหายากที่ compiler จัดการไม่ได้ หรือ profiler ยังชี้อยู่
เมื่อ manual memoization ช่วยได้จริง (วันนี้)
หัวข้อที่มีชื่อว่า “เมื่อ manual memoization ช่วยได้จริง (วันนี้)”จนกว่า compiler จะแพร่หลาย hand-memoization ยังคุ้มสำหรับบางกรณีเฉพาะ:
- การคำนวณที่ หนักจริง (sort/filter หลายพัน item, คณิตหนัก ๆ) →
useMemo - ค่า/function ที่ส่งให้ memoized child หรือเป็น effect dependency ซึ่ง reference ใหม่จะทำให้เกิด render หรือ effect รันเกิน →
useCallback/useMemo - subtree ขนาดใหญ่ที่ถูกข้ามบ่อย →
React.memo
นอกเหนือจากนั้น memoize มักมีต้นทุน (memory + การเทียบ + ความรก) มากกว่าที่ประหยัด วัดผล