介紹
在最佳化 React 應用程式效能時,開發人員通常會使用 useMemo 和 useCallback。
人们很容易将每个对象和函数包装在这些钩子中,假设缓存输出将自动加速应用程序。然而,這是一個常見的誤解。
不必要的記憶不僅會使程式碼更難閱讀,而且還會使程式碼更難閱讀。它增加了淺依賴項比較和垃圾收集記憶體分配的效能開銷。
本文概述了這些鉤子有益的具體場景以及何時應避免它們。
1. 記憶的隱性成本
useMemo 和 useCallback 都不是自由操作。它們在每個渲染週期都會產生運行時成本:
- 依賴關係比較開銷:
在每次渲染時,React 必須淺比較(使用
Object.is)依賴項數組中的每個值。執行這些陣列檢查會增加 CPU 開銷。 - 記憶體保留: 保留對先前函數或計算值的參考可以防止它們被垃圾收集,從而增加應用程式的記憶體佔用。
- 複雜性與錯誤: 錯誤配置的依賴數組可能會導致陳舊的閉包,其中函數引用過時的狀態或道具。
2. useMemo 使用指南
僅在以下兩種情況下使用 useMemo:
場景 A:計算量大的任務
將其用於 CPU 密集型操作,例如對大型數組進行排序、過濾複雜資料集或運行繁重的映射操作。
- 推薦用例:
// Memoize search calculations on large lists
const filteredItems = useMemo(() => {
return heavySearch(items, query);
}, [items, query]);
場景 B:保留 React.memo 的物件引用
React 在每次渲染時重新實例化元件內部的物件聲明,更改它們的記憶體位址。
如果一個物件作為 prop 傳遞給使用 React.memo 最佳化的子元件,則子元件無論如何都會重新渲染,因為物件參考發生了變化。使用 useMemo 保持引用穩定。
- 推薦用例:
// Stabilize the options object reference
const options = useMemo(() => ({ color: themeColor }), [themeColor]);
// Sibling components optimized with React.memo will not re-render unnecessarily
return <HeavyChildComponent options={options} />;
3. useCallback使用指南
useCallback 的目的不是防止函數重新實例化,而是跨渲染保留函數的記憶體參考。
在這種情況下僅套用 useCallback :
將回調傳遞給記憶的子組件
如果父元件重新呈現,則在其內部聲明的任何函數都會重新創建,從而更改它們的參考。如果這些回呼作為 props 傳遞給包裝在 React.memo 中的子元件,則無論記憶如何,子元件都會重新渲染。
使用 useCallback 保持引用穩定。
- 推薦用例:
// Stabilize click callback references
const handleClick = useCallback(() => {
console.log("Button clicked!");
}, []);
// Prevents re-renders if MemorizedButton uses React.memo
return <MemorizedButton onClick={handleClick} />;
- 重要提示:
如果子元件未包裝在
React.memo中,當父元件包裝在React.memo中時,無論您是否使用useCallback,它都會重新渲染。在這種情況下,鉤子會增加不必要的開銷。
結論:React 19 和 React 編譯器
隨著 React 19 的發布,React 編譯器 (React Forget) 在建置時會自動處理記憶。這減少了手動寫入 useMemo 和 useCallback 的需要。
手動優化時:
- **首先進行設定檔以驗證計算是否確實導致渲染延遲。 **
- **除非目標子元件使用
React.memo,否則避免包裝回呼。 ** - **優先考慮編寫乾淨、簡單的 JavaScript 邏輯,而不是先發制人的優化。 **
遵循這些準則可以使您的 React 程式碼具有可讀性和高效能。

