Featured image of post 停止在 React 中過度使用 useMemo 和 useCallbackFeatured image of post 停止在 React 中過度使用 useMemo 和 useCallback

停止在 React 中過度使用 useMemo 和 useCallback

分析 React 中 useMemo 和 useCallback 的隱藏表現開銷,並建立何時應用記憶化的指南。

介紹

在最佳化 React 應用程式效能時,開發人員通常會使用 useMemouseCallback

人们很容易将每个对象和函数包装在这些钩子中,假设缓存输出将自动加速应用程序。然而,這是一個常見的誤解。

不必要的記憶不僅會使程式碼更難閱讀,而且還會使程式碼更難閱讀。它增加了淺依賴項比較和垃圾收集記憶體分配的效能開銷

本文概述了這些鉤子有益的具體場景以及何時應避免它們。


1. 記憶的隱性成本

useMemouseCallback 都不是自由操作。它們在每個渲染週期都會產生運行時成本:

  1. 依賴關係比較開銷: 在每次渲染時,React 必須淺比較(使用 Object.is)依賴項數組中的每個值。執行這些陣列檢查會增加 CPU 開銷。
  2. 記憶體保留: 保留對先前函數或計算值的參考可以防止它們被垃圾收集,從而增加應用程式的記憶體佔用。
  3. 複雜性與錯誤: 錯誤配置的依賴數組可能會導致陳舊的閉包,其中函數引用過時的狀態或道具。

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) 在建置時會自動處理記憶。這減少了手動寫入 useMemouseCallback 的需要。

手動優化時:

  1. **首先進行設定檔以驗證計算是否確實導致渲染延遲。 **
  2. **除非目標子元件使用 React.memo,否則避免包裝回呼。 **
  3. **優先考慮編寫乾淨、簡單的 JavaScript 邏輯,而不是先發制人的優化。 **

遵循這些準則可以使您的 React 程式碼具有可讀性和高效能。