在了解React.memo的用途之前,複習一下React的幾個重要觀念:
- Component只會在state、props或context有更新的時候才會re-evaluate;
- Component re-evaluate的時候會連帶讓child components跟著 re-evaluate;
- 傳入child components的props無論有無改變都會跟著parent component一起re-evaluate;
- 如果child components還有自己的child component chain,則這些components也會跟著re-evaluate。
React.memo
當child component的sub-component tree非常龐大的,可以適當使用 React.memo
避免不必要的re-evaluate。
React.memo
會比較之前的props和parent component更新後的props有無不同,若相同則不會re-evaluate這個component,連帶也不會跟著re-evaluate的child components。
範例
Parent component
// App.js (parent component)
import {useState} from 'react';
import Me from './Me';
import Button from './Button';
export default function App(){
console.log('App loads');
const [isMe, setIsMe] = useState(false);
const addMe = (event) => {
event.preventDefault();
setIsMe((prevMe) => !prevMe);
};
return (
<>
<h1>h1 always here</h1>
<Me me={false}/>
<Button onClick={addMe}>Click and Add Me!</Button>
</>
)
}
App component's child components
// Me.js (App's child component)
import React from "react";
function Me(props){
console.log("Me loads");
return <p>{props.isMe && `It's Me`}</p>;
}
export default React.memo(Me);
// Button.js (App's another child component)
import React from "react";
function Button(props){
console.log('Button loads');
return <button {...props}>{props.children}</button>;
}
export default React.memo(Button);
範例Me.js的 React.memo(Me)
這段會比較傳入props值(isMe
)和前一次有無不同,範例內的isMe
props刻意設為 false
;由於每次都是傳入 false
,所以 React.memo(Me)
這段會讓Me component在parent component更新的時候不會跟著更新:
要注意的是App.js每次re-render都會產生長得一模一樣卻儲存在不同位置的新 addMe
函式。
函式是物件,物件是屬於參考類型(reference type)變數,每次component更新時會產生一個和更新前內容相同、卻是儲存在不同位置的新物件。意思就是,每次component更新都會產生長得一模一樣的新函式,由於新函式和舊函式儲存在不同位址,同樣內容的物件儲存在不同位址就不會是同一個物件。
因此,每次更新產生的新函式就會是新的props並傳入Button component,才會讓Button.js檔案內的 React.memo()
沒有作用。
文章最後面會附上範例的live demo,可以觀察一下console;也可以fork回去,比較一下拿掉 React.memo
前後的Me component更新狀況有無不同。
References
React - The Complete Guide (incl Hooks, React Router, Redux)
Reference vs Primitive Values