ข้ามไปยังเนื้อหา

Memoization & the Compiler

ทุก render จะรัน component ใหม่ ซึ่งสร้าง value ทุกตัวข้างในใหม่หมด — รวมถึง object, array และ function พวกนี้เป็น reference ใหม่ทุกครั้ง แม้เนื้อหาจะเหมือนเดิม ปกติแล้วไม่เป็นไร แต่เป็นปัญหาใน 2 กรณี:

  1. การคำนวณที่แพง รันทุก render ทั้งที่ input ไม่เปลี่ยน
  2. reference ใหม่ (function หรือ object) ที่ส่งให้ memoized child ทำลาย memoization ของ child ทำให้ re-render โดยไม่จำเป็น

tool memoization สามตัวจัดการคนละส่วน:

Toolcacheใช้เมื่อ
useMemo(fn, deps)value (ผลของ fn)การคำนวณแพง หรือต้องการ object/array reference ที่ stable
useCallback(fn, deps)function (reference ที่ stable)ส่ง callback ให้ memoized child (คือ useMemo สำหรับ function)
React.memo(Component)render ของ componentcomponent re-render ด้วย props เดิมและการ render นั้น costly
// Expensive calc, cached until inputs change:
const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// Stable function reference so a memoized child does not re-render:
const handleSelect = useCallback((id) => setSelected(id), []);
// Skip re-render when props are shallowly equal:
const Row = React.memo(function Row({ item }) { /* ... */ });

memoization ไม่ฟรี: เสีย memory และเสียการเทียบทุก render ใส่ไว้ทุกที่ “เผื่อไว้” ทำให้ code รกและอาจติดลบสุทธิ memoization ช่วยจริงเมื่อ:

  • การคำนวณที่ cache นั้นแพงแบบวัดได้ หรือ
  • ต้องการ reference ที่ stable เพื่อกัน React.memo child (หรือ effect dependency) ไม่ให้รันใหม่

สำหรับค่าที่ถูก — string concat, .filter เล็ก ๆ — คำนวณตอน render ดีกว่าและชัดกว่า อย่าหยิบ useMemo มาใช้แบบอัตโนมัติ

flowchart TB
  render["parent re-renders"] --> newfn["new function reference
created each render"]
  newfn --> child["memoized child sees
a 'changed' prop"]
  child --> rerender["child re-renders needlessly"]
  fix["useCallback → stable reference"] --> skip["memoized child skips re-render"]
referential equality คือเหตุผลที่ callback ทำลาย memoized child

React มี compiler ที่ memoize component และ value ของคุณอัตโนมัติตอน build — compiler แทรกสิ่งที่เทียบเท่า useMemo/useCallback/memo ให้ตรงจุดที่ช่วยจริง โดยวิเคราะห์ code ของคุณ จุดประสงค์ทั้งหมดคือ คุณไม่ควรต้อง memoize เอง

แนวทางปฏิบัติสำหรับ project React 19+:

  • เปิด compiler: เขียน code ตรงไปตรงมา อย่าโปรย useMemo/useCallback — compiler จัดการ memoization ให้ ถูกต้องกว่าและละเอียดกว่าการจูนมือส่วนใหญ่ หน้าที่คุณคือทำให้ component pure (compiler พึ่ง Rules of Hooks และ purity เพื่อทำงานอย่างปลอดภัย)
  • ไม่เปิด compiler: tool มือด้านบนยังใช้ได้ — หยิบมาเฉพาะที่ bottleneck จริงที่วัดได้

ทิศทางชัดเจน: manual memoization กำลังกลายเป็น optimization ทางเลือกสุดท้าย ไม่ใช่นิสัยเริ่มต้น ทำให้ component pure แล้วให้ compiler จัดการงานจดจำให้

useCallback ทำอะไร?
ทำไมการส่ง inline function ให้ React.memo child ถึงทำลาย memoization?
แนวทางเมื่อเปิด React Compiler คืออะไร?
เมื่อไร manual memoization คุ้มจริง (ตอนไม่มี compiler)?