在 react 中如果數(shù)據(jù)沒發(fā)生變化,則真實(shí)的 dom 不會(huì)發(fā)生改變。但是 dom 不發(fā)生改變并不代表 react 中不會(huì)產(chǎn)生其他耗時(shí)的計(jì)算。如果一個(gè)組件會(huì)產(chǎn)生大量的子組件,那單單是這些子組件 diff 就會(huì)產(chǎn)生大量的耗時(shí)操作,在某些場(chǎng)景下可能會(huì)引起頁面卡頓。
一個(gè)例子
這里有一個(gè)典型的例子,父組件 App 中有一個(gè)輸入框和子組件 Child。Child 接受來自父組件的狀態(tài) text。從代碼中可以看出,這個(gè) text 一直保持不變,絲毫不受 input 的影響。那么如果這時(shí)候輸入的值發(fā)生了改變,子組件是否會(huì) render 呢(這里指子組件是否會(huì)被調(diào)用并且 diff,并不是指真實(shí)的 dom render)?
function Child(props){
console.info("child call render")
return (
<p>{this.props.text}</p>
)
}
function App() {
const [value, setValue] = useState()
const [text, setText] = useState({text: 'hello'})
return (
<>
<input onChange = {e => setValue(e.target.value)} />
<Child text={hello}/>
</>
)
}
答案是: 會(huì)!。也就是雖然父組件傳遞給子組件的值沒有發(fā)生任何改變,但實(shí)際上子組件仍然會(huì) render。其原因也非常簡(jiǎn)單:
- react 如果判斷本次 props 和上一次 props 不一致,則一定會(huì) render 該組件,但是這個(gè)不一致指的是
===而不是 props 的內(nèi)容。 -
<Child text={hello}/>實(shí)際上是轉(zhuǎn)換成了React.createElement(Child, {text: 'hello'}))。而這里的{text: 'hello'}則是傳遞給 Child 的 props。每當(dāng) App 狀態(tài)發(fā)生改變時(shí),都會(huì)創(chuàng)建一個(gè)新的匿名對(duì)象{text: 'hello'}。雖然內(nèi)容不同,但是引用卻改變了。因此導(dǎo)致了 Child render。
React.memo
這樣的情況,類組件可以通過 componentShouldUpdate 來實(shí)現(xiàn)用戶自定義的比較。而對(duì)于函數(shù)組件在沒有對(duì)應(yīng)的生命周期的情況下,則可以采用 React.memo 來解決這個(gè)問題。React.memo 接受兩個(gè)參數(shù):
- 需要 memo 的組件
- compare 方法(默認(rèn)為
shallowEqual)
即利用該方法可以實(shí)現(xiàn)與 componentShouldUpdate 相似的功能,每次比較時(shí)不采用 === 而是采用 compare 方法,當(dāng)該方法返回 true 則表示 props 沒有變化。shallowEqual 也非常簡(jiǎn)單:
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
而 React.memo 的實(shí)現(xiàn)更是非常簡(jiǎn)潔:
function memo(type, compare) {
{
if (!isValidElementType(type)) {
error('memo: The first argument must be a component. Instead ' + 'received: %s', type === null ? 'null' : typeof type);
}
}
return {
$$typeof: REACT_MEMO_TYPE,
type: type,
compare: compare === undefined ? null : compare
};
}
幾乎沒有做任何事情,只是將該組件聲明為 REACT_MEMO_TYPE 告訴 React 比較時(shí)不要采用 === 而是采用用戶自定義的比較函數(shù)或者默認(rèn)采用 shallowEqual。