避免非必要的重複渲染(re-render) 是性能優化的一個重點。在大部分情況下,re-render 並不會對性能造成明顯的影響,除非是渲染頻繁或者使用複雜的運算。
在引入 memoization 前,應先檢查以下幾點,避免不必要的重複渲染,這樣可以減少不必要的優化:
useState
, useEffect
或 custom Hook 放在正確合適的位置useEffect
useEffect
中加上不必要的 dependencies,像是如果函式只有在 useEffect
使用,可以移到 useEffect
中可以用以下工具來評估運算是否足夠複雜到需要優化:
console.time
和 console.timeEnd
:可以用來計算特定運算的執行時間。範例如下:
console.time("filter array");
const visibleTodos = filterTodos(todos, tab);
console.timeEnd("filter array");
如果狀態相關的內容,在需要複雜運算的元件上方的某個位置使用時,很容易會使用 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.memo
為例,當元件使用 React.memo
來包裝時,會記錄最近的 props 和渲染結果。當元件接收到新 props 時,React 會檢查這些 props 是否與之前的 props 相同。如果 props 相同,React 會返回之前的渲染結果(即快取的結果)。如果 props 不同,則會重新計算並渲染新的結果。
React.memo
是比較 props ,而 useMemo
和 useCallback
則是比較 dependencies 的內容。
React.memo
是一個 higher-order component,用來包住元件以避免不必要的重新渲染。透過比較新舊的 props 是否相同來決定是否重新渲染,使用 Object.is
比較,React.memo
也有第二個可選參數用來自訂比較的方法,會是一個接受兩個參數的函數:元件之前的 props 和新的 props。
使用時機:經常使用相同的 props 重新渲染時。
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
useCallback
是用來記住某個函數的引用,避免函數在每次渲染時重新創建。而 useMemo
則是用來記住一段昂貴計算的結果。透過 dependencies 用來決定是否重新渲染,並使用 Object.is
來比較兩個值是否相等。
使用時機:
React.memo
中,因為每次需渲染時物件和函數會重新創建。