React memo

在 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)單:

  1. react 如果判斷本次 props 和上一次 props 不一致,則一定會(huì) render 該組件,但是這個(gè)不一致指的是 === 而不是 props 的內(nèi)容。
  2. <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ù):

  1. 需要 memo 的組件
  2. 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。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容