Skip to main content

useCallback 與 useMemo 的關係與演進

在學 React Hooks 的過程中,常常會遇到兩個看似相似的 Hook:useCallbackuseMemo
他們的名字分別帶有 callback(回呼函數)memo(記憶),直覺上會以為一個記憶函數、一個記憶結果。
那麼,這兩個 Hook 之間到底有什麼關聯?useCallback 真的是從 useMemo 演變而來的嗎?

讓我們來完整釐清!


一、核心差異

  1. useMemo:記住「某個運算結果」

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    • ab 沒有改變時,不會重新計算 computeExpensiveValue,而是回傳之前記住的結果。
  2. useCallback:記住「某個函數本身」

    const memoizedCallback = useCallback(() => {
    doSomething(a, b);
    }, [a, b]);
    • 當依賴沒有改變時,這個函數的參考(reference)保持不變,避免子元件或副作用因為函數物件的新建而重新觸發。

二、技術上的關聯

其實 useCallback 就是 useMemo 的語法糖 (syntactic sugar)
換句話說,以下兩種寫法是等價的:

// useCallback
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

// 等價於 useMemo
const memoizedCallback = useMemo(() => {
return () => doSomething(a, b);
}, [a, b]);

所以可以說:useCallback 是由 useMemo 演變出來的,目的只是讓「記憶函數」這個需求更直觀、更好閱讀。


三、為什麼要拆成兩個 Hook?

React 團隊雖然可以只提供 useMemo,但還是另外做了一個 useCallback,主要原因有三個:

  1. 語意清楚

    • useMemo:你想要記住的是「值」

    • useCallback:你想要記住的是「函數」

  2. 可讀性
    如果一律用 useMemo,你需要進一步理解裡面到底回傳的是值還是函數,會降低直觀度。

  3. 最佳化習慣
    React 官方一再提醒:不要過度使用 useMemouseCallback,因為 memoization 本身有開銷。
    把兩個需求分開,有助於開發者在適當的地方才使用。


四、演進脈絡

  1. 早期(React 16.8 以前)

    • 想避免函數重建:手動在 class component 綁定 this.method = this.method.bind(this)

    • 想避免重複計算:自己實作快取邏輯。

  2. React 16.8(Hooks 引入)

    • 推出 useMemo 來快取計算結果。

    • 同時推出 useCallback,雖然底層等於 useMemo,但讓「函數記憶」語意化。

  3. 現在

    • 官方建議只在「性能瓶頸」時才用這些 Hook。

    • 常見場景:避免子元件重複渲染(搭配 React.memo),或避免 effect 因函數參考變動而重新執行。


五、快速對照表

Hook記憶的對象回傳值常見使用情境
useMemo運算結果計算過的值(非函數)- 計算昂貴運算的結果(例如:排序、大量計算)\
- 避免每次 render 都重算
useCallback函數參考穩定的函數物件(function)- 搭配 React.memo 避免子元件重複渲染\
- 避免 effect 依賴變化而重新觸發

六、結論

  • useMemouseCallback 本質上同源,useCallback 就是 useMemo 的語法糖

  • 差別在於「記憶結果」和「記憶函數」。

  • React 團隊拆開兩個 Hook,是為了語意清晰、程式可讀性、最佳化提醒

  • 使用時要有節制,避免為了「好像比較專業」而過度使用。


✨ 小提醒:真正的效能最佳化,往往要搭配 profiling 工具 找出瓶頸,不要預先過度優化。