iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Modern Web

前進React 生態系 : 技術應用與概念解析系列 第 23 篇

Day 23 - 掌握 React Memoization : React.Memo 、useMemo 和 useCallback

  • 分享至 

  • xImage
  •  

在使用 memoization 之前

為什麼需要使用 Memoization ?

避免非必要的重複渲染(re-render) 是性能優化的一個重點。在大部分情況下,re-render 並不會對性能造成明顯的影響,除非是渲染頻繁或者使用複雜的運算。

使用前檢查事項

在引入 memoization 前,應先檢查以下幾點,避免不必要的重複渲染,這樣可以減少不必要的優化:

  • 是否將 useState , useEffect 或 custom Hook 放在正確合適的位置
  • 是否有非必要的 useEffect
  • 是否在 useEffect 中加上不必要的 dependencies,像是如果函式只有在 useEffect 使用,可以移到 useEffect 中
  • 是否將 Component 拆分的足夠小且明確,另外最好不要在一個 Component 中又 render 一個 Component
  • 和 state 無關的函式是否在元件外面宣告
  • 是否是非常複雜的運算

什麼算是「非常複雜的計算」?

可以用以下工具來評估運算是否足夠複雜到需要優化:

  • React DevTools Profiler:用來觀察元件的渲染時間和次數。
  • console.time 和 console.timeEnd:可以用來計算特定運算的執行時間。

範例如下:

console.time("filter array");
const visibleTodos = filterTodos(todos, tab);
console.timeEnd("filter array");

優化 re-render 的小技巧

如果狀態相關的內容,在需要複雜運算的元件上方的某個位置使用時,很容易會使用 React.memo 來處理,但實際上可以將狀態相關的內容封裝成一個元件。

使用前:

export default function App() {
  const [color, setColor] = useState("red");
  return (
    <div style={{ color }}>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p>Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
}

使用後:

export default function App() {
  return (
    <ColorPicker>
      <p>Hello, world!</p>
      <ExpensiveTree />
    </ColorPicker>
  );
}

function ColorPicker({ children }) {
  const [color, setColor] = useState("red");
  return (
    <div style={{ color }}>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      {children}
    </div>
  );
}

ColorPicker 元件內的狀態不會影響到 ExpensiveTree 的渲染,減少不必要的 re-render。

React 的 memoization 是怎麼做運作的?

React 的 memoization 機制針對每個元件或計算只儲存一個快取結果。

以 React.memo 為例,當元件使用 React.memo 來包裝時,會記錄最近的 props 和渲染結果。當元件接收到新 props 時,React 會檢查這些 props 是否與之前的 props 相同。如果 props 相同,React 會返回之前的渲染結果(即快取的結果)。如果 props 不同,則會重新計算並渲染新的結果。

React.memo 是比較 props ,而 useMemo 和 useCallback 則是比較 dependencies 的內容。

React.memo

React.memo 是一個 higher-order component,用來包住元件以避免不必要的重新渲染。透過比較新舊的 props 是否相同來決定是否重新渲染,使用 Object.is 比較,React.memo 也有第二個可選參數用來自訂比較的方法,會是一個接受兩個參數的函數:元件之前的 props 和新的 props。

使用時機:經常使用相同的 props 重新渲染時。

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

useCallback 和 useMemo

useCallback是用來記住某個函數的引用,避免函數在每次渲染時重新創建。而 useMemo 則是用來記住一段昂貴計算的結果。透過 dependencies 用來決定是否重新渲染,並使用 Object.is 來比較兩個值是否相等。

使用時機:

  • 當 props 被用作另一個 hook 的 dependency 。
  • 當 Component 被包在 React.memo 中,因為每次需渲染時物件和函數會重新創建。
  • 當涉及複雜運算時且 dependencies 很少改變。

上一篇
Day 22 - 掌握 React 19 Metadata 與資源載入優化
下一篇
Day 24 - React Compiler 簡介
系列文
前進React 生態系 : 技術應用與概念解析 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言