React.memo
是用來減少不必要被更新的元件被重新渲染。當父元件的資料狀態被更新時,若子元件相關的 props 沒有因此被更新時,這個子元件就不需被重新渲染。React 提供了 React.memo 來達成這個功能。
React.memo
是 一個 Higher Order Component。
const MemoMyComponent = React.memo(MyComponent)
or
export default React.memo(MyComponent)
透過以上語法,將子元件包在 React.memo
之中,React 發現 props 相同時,會忽略 Render 這個子元件,並直接重用上次的 Render 結果
這預設只會對 props 進行 shallow compare。(後面會介紹)
如果你需要控制比較的方法,你可以提供一個自訂比較的 Function 作為第二個參數。
const MyComponent = (props) => {
/* render using props */
}
const areEqual = (prevProps, nextProps) => {
// 自訂比較的 Function
}
export default React.memo(MyComponent, areEqual);
React.memo
共接收兩個參數,第一個是要包住的元件,第二個是可以自訂比較 props 的方法,回傳 false 時會重新渲染元件。
元件 props 的比對,在原生型態 (string/number/boolean) 是 call-by-value,但是在物件型態 (object/array/function) 還是 call-by-reference,所以當 props 是物件型態時,比較的是記憶體位置。
當父元件重新渲染時,傳入 props 給子元件,如果是物件型別的話,即使傳入 props 的值完全一樣,因為記憶體位置不同,還是會導致 React.memo
失效,重新渲染子元件。
定義三個子元件如下
這裡傳入二個 props,一個是 Status,一個是 Obj,在沒有使用 React.memo 時,改變 props 會觸發 Re-Render。
const Child = (props) => {
console.log(`Child render`);
return <p>Child number is : {props.obj.number}</p>;
};
const ChildMemo = React.memo((props) => {
console.log(`ChildMemo render`);
return <p>ChildMemo number is : {props.obj.number}</p>;
});
const compareFn = (prevProps, nextProps) => {
if (prevProps.obj.number !== nextProps.obj.number) {
return false;
}
return true;
};
const ChildMemoCompare = React.memo((props) => {
console.log(`ChildMemoCompare render`);
return <p>ChildMemoCompare number is : {props.obj.number}</p>;
}, compareFn);
const App = () => {
console.log('App render');
const [status, setStatus] = React.useState(false);
const [obj, setObj] = React.useState({number: 1});
const handleChangeObj = () => {
setObj({number: obj.number + 1});
};
return (
<>
<button onClick={() => setStatus(!status)}>Change Status</button>
<button onClick={handleChangeObj}>Change Object</button>
<Child status={status} obj={obj} />
<ChildMemo status={status} obj={obj} />
<ChildMemoCompare status={status} obj={obj} />
</>
);
}
觀察 ChildMemo 可以發現當 props 傳入的是物件時,即使傳入 props 的值完全一樣,因為記憶體位置不同,還是會導致 React.memo 失效,在這個範例會發現 ChildMemo 還是被重新渲染。
而 ChildMemoCompare 會再去使用 compareFn 做比對,所以當只有改變 Obj.number 的值時才會觸發 ChildMemoCompare Re-Render,改變 Status 時則不會 Re-Render。
執行結果:https://codepen.io/lala-lee-jobs/pen/bGMWaEx?editors=0011
定義二個子元件如下
const ChildMemo = React.memo((props) => {
console.log("ChildMemo Render");
return (
<div>
<span>ChildMemo</span>
<button onClick={props.reset}>ChildMemo Reset Count</button>
</div>
);
});
const ChildMemoCallback = React.memo((props) => {
console.log("ChildMemoCallback Render");
return (
<div>
<span>ChildMemoCallback</span>
<button onClick={props.reset}>ChildMemoCallback Reset Count</button>
</div>
);
});
const App = () => {
console.log("App render");
const [count, setCount] = React.useState(0);
const resetCount = () => {setCount(0)};
const resetCountUseCallback = React.useCallback(() => {
setCount(0);
}, []);
return (
<>
<div>
<span>App Count: {count}</span>
<button onClick={() => setCount((count) => count + 1)}>Increment</button>
</div>
<ChildMemo reset={resetCount} />
<ChildMemoCallback reset={resetCountUseCallback} />
</>
);
}
觀察 ChildMemo,當按下 Increment 時,雖然 ChildMemo 畫面不需變動,還是被 Re-Render;按下 ChildMemo 元件上的 Reset,ChildMemo 畫面不需變動,還是被 Re-Render。
觀察 ChildMemoCallback,當按下 Increment 時,ChildMemoCallback 畫面不需變動,不會被 Re-Render;按下 ChildMemoCallback 元件上的 Reset,ChildMemoCallback 畫面不需變動,不會被 Re-Render。
執行結果:https://codepen.io/lala-lee-jobs/pen/qBYmpgP?editors=0011
在使用 useMemo、useCallback及 memo 做優化時,要注意一些使用情境,因為處理優化也是會佔用效能,可能讓畫面 Re-Render 的效能都比處理優化的效能來得好。
一旦專案越來越複雜,不同的操作方式會變更不同的狀態,要實作出完善的功能,狀態管理就變得十分重要,接下來幾篇文章,會開始介紹 React 與狀態管理相關的各種使用情境。
https://www.w3schools.com/react/react_memo.asp
https://ithelp.ithome.com.tw/articles/10268792