useCallback 與 useMemo 的關係與演進
在學 React Hooks 的過程中,常常會遇到兩個看似相似的 Hook:useCallback 與 useMemo。
他們的名字分別帶有 callback(回呼函數) 與 memo(記憶),直覺上會以為一個記憶函數、一個記憶結果。
那麼,這兩個 Hook 之間到底有什麼關聯?useCallback 真的是從 useMemo 演變而來的嗎?
讓我們來完整釐清!
一、核心差異
-
useMemo:記住「某個運算結果」const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);- 當
a或b沒有改變時,不會重新計算computeExpensiveValue,而是回傳之前記住的結果。
- 當
-
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,主要原因有三個:
-
語意清楚
-
useMemo:你想要記住的是「值」 -
useCallback:你想要記住的是「函數」
-
-
可讀性
如果一律用useMemo,你需要進一步理解裡面到底回傳的是值還是函數,會降低直觀度。 -
最佳化習慣
React 官方一再提醒:不要過度使用useMemo與useCallback,因為 memoization 本身有開銷。
把兩個需求分開,有助於開發者在適當的地方才使用。
四、演進脈絡
-
早期(React 16.8 以前)
-
想避免函數重建:手動在 class component 綁定
this.method = this.method.bind(this)。 -
想避免重複計算:自己實作快取邏輯。
-
-
React 16.8(Hooks 引入)
-
推出
useMemo來快取計算結果。 -
同時推出
useCallback,雖然底層等於useMemo,但讓「函數記憶」語意化。
-
-
現在
-
官方建議只在「性能瓶頸」時才用這些 Hook。
-
常見場景:避免子元件重複渲染(搭配
React.memo),或避免 effect 因函數參考變動而重新執行。
-
五、快速對照表
| Hook | 記憶的對象 | 回傳值 | 常見使用情境 |
|---|---|---|---|
useMemo | 運算結果 | 計算過的值(非函數) | - 計算昂貴運算的結果(例如:排序、大量計算)\ |
| - 避免每次 render 都重算 | |||
useCallback | 函數參考 | 穩定的函數物件(function) | - 搭配 React.memo 避免子元件重複渲染\ |
| - 避免 effect 依賴變化而重新觸發 |
六、結論
-
useMemo與useCallback本質上同源,useCallback就是useMemo的語法糖。 -
差別在於「記憶結果」和「記憶函數」。
-
React 團隊拆開兩個 Hook,是為了語意清晰、程式可讀性、最佳化提醒。
-
使用時要有節制,避免為了「好像比較專業」而過度使用。
✨ 小提醒:真正的效能最佳化,往往要搭配 profiling 工具 找出瓶頸,不要預先過度優化。