React 性能優化:React.memo、useMemo、useCallback

React.memo、useMemo、useCallback 這三個是 React 性能優化一定會被提到的,今天就來比較一下他們的差異。  

一、基本概念與本質差異

  1. React.memo:組件層級(Component-level)
    是 HOC,擋在組件外部,決定「這個組件要不要 re-render」。
  2. useMemo:數值層級(Value-level)
  3. useCallback:函式引用層級(Function reference-level)
    useMemo/useCallback 都是 Hook,作用在組件內部,不會阻止 render,只是在 render 發生時降低成本(避免重算或 reference 改變)。

二、它們解決了什麼問題?

React.memo —— 控制「要不要 render」

  • 組件級優化
  • React.memo 會對 props 做 shallow comparison,props reference 沒變 → 跳過 render
  • 行為類似於 class component 的 PureComponent
const UserCard = React.memo(function UserCard({ user }) {
  // 只有當 user prop 發生變化時,這裡才會執行
  return <div>{user.name}</div>;
});

⚠️ 注意:它是 shallow comparison,不是做深度比對,所以object / array / function 只要 reference 變了,就視為「變了」

適合使用場境

  • 子組件 render 成本高(DOM 多、邏輯複雜)
  • 父組件 re-render 很頻繁
  • 子組件的 props 大多數時間不變

不適合使用場境

  • 組件很小、render 很輕,React.memo 本身有 props comparison 成本,反而會是負優化
  • props 幾乎每次都會變(memo 很難命中)

useMemo —— 控制「要不要重新計算」

  • 記憶「計算結果」
  • 避免每次 Render 都重跑耗時的複雜運算(如大量的 Array filter/sort)。
// 只有當 data 改變時,才會重新執行 expensiveCalc
const total = useMemo(() => expensiveCalc(data), [data]);

適合使用場境

  • 計算成本明顯(資料量大、邏輯複雜)
  • render 次數多,但 input(dependency)很少改變
  • 計算結果會被多次使用

不適合使用場境

  • 輕量計算(例如簡單 map、加減)
  • 只為了「看起來比較專業」

useCallback —— 控制「Function Reference 是否穩定」

  • 記憶 function 本身
  • 本質等同於 useMemo(() => fn, deps)
// 確保 handleClick 在多次 render 之間是同一個 reference
const handleClick = useCallback(() => {
  console.log('Clicked');
}, []);

// 這樣傳給 Child 時,才不會因為 handleClick 變了而導致 Child 無謂重繪
<Child onClick={handleClick} />

通常是為了配合 React.memo 使用,
避免因為 function reference 改變,導致 memo 失效。

適合使用場境

  • 傳遞 function 給被 React.memo 包住的子組件
  • function 作為 useEffect / useMemo 的 dependency
  • function 建立成本高,且 dependency 穩定

不適合使用場境

  • function 只在本組件使用
  • 沒有 memo 子組件,也沒 dependency 問題

三、優化決策流程

效能優化本身也有成本,一個重點是,避免過早優化(Premature Optimization),更不要為了優化而無差別套用 React.memo

    Step 1:先量測,再動手
    • 使用 React Developer Tools Profiler
    • 或 Chrome DevTools Performance
    • 重點觀察:
      • 哪些組件 re-render 很頻繁
      • 哪些組件 render 本身很慢
      • 是否存在明顯「不必要的 re-render」
    Step 2:確認瓶頸在哪裡
    場景 A:render 次數多,但每次 render 都在做重計算

    例如:

    • 幾千筆資料的 filter / sort / reduce
    • render 觸發頻繁,但 input 幾乎不變

    👉 使用 useMemo 記住計算結果,避免重算。

    場景 B:子組件 render 成本高,且 props 多數時間不變

    例如:

    • DOM 結構複雜
    • 父組件狀態頻繁變動,導致子組件有不必要的 re-render 影響效能或體驗

    👉 使用 React.memo,在組件層級擋掉不必要的 render。

    場景 C:已使用 React.memo,但仍然頻繁 re-render

    這通常不是 memo 沒用,而是 props reference 不穩定

    請檢查子組件 props 是否包含:

    • function
      • 若 function 每次 render 都重新建立
      • 且會傳給被 memo 的子組件
        👉 父組件可考慮使用 useCallback 穩定 reference
    • object / array
      • 若每次 render 都新建
      • 且實際造成 memo 失效
        👉 視情況使用 useMemo 穩定 reference

    Similar Posts