在 react 中如果數(shù)據(jù)沒發(fā)生變化,則真實的 dom 不會發(fā)生改變章蚣。但是 dom 不發(fā)生改變并不代表 react 中不會產生其他耗時的計算悉默。如果一個組件會產生大量的子組件曲梗,那單單是這些子組件 diff 就會產生大量的耗時操作,在某些場景下可能會引起頁面卡頓弄跌。
一個例子
這里有一個典型的例子诱建,父組件 App 中有一個輸入框和子組件 Child。Child 接受來自父組件的狀態(tài) text碟绑。從代碼中可以看出俺猿,這個 text 一直保持不變,絲毫不受 input 的影響格仲。那么如果這時候輸入的值發(fā)生了改變押袍,子組件是否會 render 呢(這里指子組件是否會被調用并且 diff,并不是指真實的 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}/>
</>
)
}
答案是: 會!谊惭。也就是雖然父組件傳遞給子組件的值沒有發(fā)生任何改變,但實際上子組件仍然會 render。其原因也非常簡單:
- react 如果判斷本次 props 和上一次 props 不一致圈盔,則一定會 render 該組件豹芯,但是這個不一致指的是
===
而不是 props 的內容。 -
<Child text={hello}/>
實際上是轉換成了React.createElement(Child, {text: 'hello'}))
驱敲。而這里的{text: 'hello'}
則是傳遞給 Child 的 props铁蹈。每當 App 狀態(tài)發(fā)生改變時,都會創(chuàng)建一個新的匿名對象{text: 'hello'}
众眨。雖然內容不同握牧,但是引用卻改變了。因此導致了 Child render娩梨。
React.memo
這樣的情況沿腰,類組件可以通過 componentShouldUpdate
來實現(xiàn)用戶自定義的比較。而對于函數(shù)組件在沒有對應的生命周期的情況下狈定,則可以采用 React.memo
來解決這個問題颂龙。React.memo 接受兩個參數(shù):
- 需要 memo 的組件
- compare 方法(默認為
shallowEqual
)
即利用該方法可以實現(xiàn)與 componentShouldUpdate
相似的功能,每次比較時不采用 === 而是采用 compare 方法纽什,當該方法返回 true 則表示 props 沒有變化厘托。shallowEqual 也非常簡單:
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 的實現(xià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ù)或者默認采用 shallowEqual
稿湿。