我有一支技術全面、經驗豐富的小型團隊,專注高效交付中等規模外包項目,有需要外包項目的可以聯系我
很多代碼庫到處都是?useCallback
?/?useMemo
。初衷是好的:減少不必要的重新渲染、穩定引用、提速。然而,用錯場景或鋪天蓋地地包一層,往往只會帶來樣板代碼、脆弱的依賴、以及幾乎沒有的收益。下面把常見誤區、根因與替代方案一次講清。
為什么大家老是手伸向 useCallback / useMemo?
想避免重復渲染:組件反復 render 看起來“慢”,于是希望“讓引用穩定”來觸發更少的 diff。
函數/對象每次 render 都是新引用:擔心子組件比較不過,索性全都包一層。
期望優化,但缺少度量:沒有 Profile 前置驗證,拍腦袋優化極易南轅北轍。
然而,并非所有函數都需要“穩定”,尤其是子組件沒有被?React.memo
?包裹的時候;此外,依賴數組里塞不穩定的對象/函數,又會讓 effect?次次觸發——這就是陷阱的源頭。
陷阱一:事件處理器的“過度穩定化”
很多人會無差別地把事件處理器包上?useCallback
,期望“更穩更快”。但只有當子組件是?React.memo
,穩定引用才有意義;否則根本不參與比較。
// ? 不要這樣把臨時對象和內聯函數傳給已 memo 的組件
function?Meh()?{return?(<MemoizedComponentvalue={{?hello:?'world' }}onChange={(result)?=>?console.log('result')}/>)
}// ? 需要穩定引用時,再用 useMemo / useCallback
function?Okay()?{const?value = useMemo(()?=>?({?hello:?'world'?}), [])const?onChange = useCallback((result) =>?console.log(result), [])return?<MemoizedComponent?value={value}?onChange={onChange}?/>
}
再看一個常見寫法:
function?MyButton()?{const?onClick = useCallback((event) =>?console.log(event.currentTarget.value),[])return?<button?onClick={onClick}?/>
}
這里按鈕沒有被?React.memo
?包裹,因此傳入的?onClick
?是否“穩定”并不會改變渲染行為。而?useCallback
?本身也要參與一次創建/比對,徒增復雜度——收益≈0。
小結:只有當“接收方”基于引用做淺比較(如?
React.memo
)時,穩定才有意義;否則純屬樣板。
陷阱二:依賴數組 + Props,極易觸發“連環反應”
當某個函數/對象被放入?useEffect
?的依賴數組,React 會用?Object.is
?做淺比較。只要引用不穩定,effect 就會每次 render 都重跑,從而抵消掉你以為的“穩定化”。
function OhNo({ onChange }) {const handleChange = useCallback((e: React.ChangeEvent) => {trackAnalytics('changeEvent', e)onChange?.(e)}, [onChange])return <SomeMemoizedComponent onChange={handleChange} />
}// 調用方:
<OhNo onChange={() => props.doSomething()} />
調用方把一個臨時箭頭函數傳了進來,onChange
?引用每次都不同,于是?handleChange
?也被迫每次重建;結果是——你以為穩定了,其實全白搭。
再看一個更“真”的例子(熱鍵):
export?function?useHotkeys(hotkeys: Hotkey[])?{const?onKeyDown = useCallback(()?=>?{?/* ... */?}, [hotkeys])useEffect(()?=>?{document.addEventListener('keydown', onKeyDown)return?()?=>?document.removeEventListener('keydown', onKeyDown)}, [onKeyDown])
}
如果?hotkeys
?是調用方每次都新建的數組,那?onKeyDown
?就一定每次變化,事件監聽也會每次解綁/重綁。把“穩定”的責任推給所有調用方,不僅脆弱,還難以排錯。
結論:在依賴鏈里塞“會變”的引用,再多的 useCallback 都救不了;這不是性能優化,而是“反應式地雷”。
更靠譜的做法:Ref 持久化 + 漸進更新
一個實戰穩定的模式是:用?ref
?持有最新值,在 effect 中只綁定一次,處理邏輯里讀取 ref。這樣既保證處理器穩定,又能拿到最新數據。
export?function?useHotkeys(hotkeys: Hotkey[])?{// 1) 用 ref 持久化數據const?hotkeysRef = useRef(hotkeys)// 2) 每次 render 同步最新值(不加依賴,始終最新)useEffect(()?=>?{hotkeysRef.current = hotkeys})// 3) 穩定的處理器,不依賴外部變化const?onKeyDown = useCallback((e: KeyboardEvent) =>?{const?latest = hotkeysRef.current// ... 用 latest 做判斷/匹配}, [])// 4) 只綁定一次監聽器useEffect(()?=>?{document.addEventListener('keydown', onKeyDown)return?()?=>?document.removeEventListener('keydown', onKeyDown)}, [])
}
因此,事件監聽不會反復重綁;
同時,回調里讀到的是最新
hotkeys
;盡管如此,外層組件怎么傳都不再牽動內部結構。
很多庫(例如?React Query)在內部就采用類似思路:ref 驅動“讀最新”,effect 驅動“只綁定一次”。
React 19 的原生解法:useEffectEvent
(提案)
React 計劃提供?useEffectEvent
?來表達“非反應式事件”:回調引用穩定,內部總能讀到最新值,且不會把依賴向外蔓延。
export?function?useHotkeys(hotkeys: Hotkey[])?{// onKeyDown 本身穩定,但其內部每次讀取到的都是“最新 hotkeys”const?onKeyDown = useEffectEvent((e: KeyboardEvent) =>?{// 使用 hotkeys,始終為最新})useEffect(()?=>?{document.addEventListener('keydown', onKeyDown)return?()?=>?document.removeEventListener('keydown', onKeyDown)}, [])
}
于是,不必再手搓?
ref
;而且,避免了“依賴數組失控”;
最后,事件回調既穩定又不陳舊,語義清晰。
什么時候該用,什么時候真別用?
可以用的場景(有實打實回報):
向?
React.memo
?子組件傳參:確實需要穩定引用避免無意義重渲。昂貴計算的結果緩存:
useMemo
?封裝重活,有測量再上。穩定的訂閱/解綁回調:配合?
ref
?/?useEffectEvent
,只綁一次。
應當避免的用法(基本白忙活):
給未 memo 化的子組件一律包 useCallback。
把不穩定的對象/函數塞進依賴數組,導致effect 每次觸發。
沒有基準測試就“憑感覺”到處加?
useMemo
/useCallback
。
簡單決策樹:
子組件?不是
React.memo
?→?別為了“穩定”而穩定。依賴鏈中存在臨時引用?→ 用?
ref
/useEffectEvent,消除連鎖依賴。計算確實重且被復用?→?
useMemo
,并用 Profile 證明。
關鍵要點回看
useCallback
?不是銀彈;在缺少?React.memo
?或依賴不穩時,它只會徒增復雜度。依賴數組的穩定性 > 回調的“看起來穩定”;錯位的穩定會讓 effect?每次重跑。
Ref + 一次性綁定(或?
useEffectEvent
)是事件類場景的更健壯模式。先測量再優化:用 DevTools Profiler/
why-did-you-render
?等工具,用數據說話,再決定是否上?useMemo
?/?useCallback
。
Final Takeaway
今天系統梳理了為何?useCallback
/useMemo
常常事與愿違,以及如何用?ref
?持久化 + 一次性綁定(或即將到來的?useEffectEvent
)更直接地解決問題。優化要以穩定的依賴鏈為前提,而不是到處包一層“看起來更專業”的 Hook。
前端AI·探索:涵蓋動效、React Hooks、Vue 技巧、LLM 應用、Python 腳本等專欄,案例驅動實戰學習,點擊二維碼了解更多詳情。
最后:
深入React:從基礎到最佳實踐完整攻略
python 技巧精講
React Hook 深入淺出
CSS技巧與案例詳解
vue2與vue3技巧合集