react memo詳解

最近在接觸到的幾個項目發(fā)現(xiàn),有的項目使用很多地方用到了 React.memo,有的項目一次都沒有使用。那么,項目中到底要不要使用 memo 呢?看完這篇文章,你就會有答案。

1、什么是memo

???? ????memo 允許你的組件在 props 沒有改變的情況下跳過重新渲染。

1.1、用法

const Greeting = React.memo(function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
});

export default Greeting;
參數(shù)

???? ????Component:要進行記憶化的組件。memo 不會修改該組件,而是返回一個新的、記憶化的組件。它接受任何有效的 React 組件,包括函數(shù)組件和 forwardRef 組件。
???? ????arePropsEqual(可選):一個函數(shù),接受兩個參數(shù):組件的前一個 props 和新的 props。如果舊的和新的 props 相等,即組件使用新的 props 渲染的輸出和表現(xiàn)與舊的 props 完全相同,則它應(yīng)該返回 true。否則返回 false。通常情況下,你不需要指定此函數(shù)。默認情況下,React 將使用 Object.is 比較每個 prop。

1.2、與 PureComponent 有何差別

???? ????PureComponent 適用于類組件,當(dāng) props 和 state 與之前保持一致時會跳過重新渲染。
???? ????memo 適用于函數(shù)組件,當(dāng) props 和之前一致時,會跳過重新渲染。這里不包含 state,React 官方的說法是在函數(shù)組件中,即使沒有 memo,調(diào)用具有相同 state 的 set 函數(shù),默認已經(jīng)阻止了重新渲染。這句話不太容易理解,翻譯一下就是當(dāng) setState 的值和引用沒有變化時,React 會阻止組件的重新渲染。

2、代碼解析

2.1、源碼

function memo(type, compare) {
  // 核心代碼
  return {
    $$typeof: REACT_MEMO_TYPE,
    type: type,
    compare: compare === undefined ? null : compare
  };
}

???? ????memo 的源碼比較簡單,在返回一個記憶化組件的同時,給組件添加 $$typeof 標記。在創(chuàng)建 Fiber 時,會根據(jù) $$typeof 的值,給 fiber.tag 賦值為 MemoComponent(MemoComponent=14)。

2.2、工作流

???? ????當(dāng) state(可以是任何組件的 state)改變時,React 會從根節(jié)點開始遍歷 Fiber 樹,在這個過程中會對每一個 Fiber 節(jié)點調(diào)用 beginWork 方法。當(dāng) tag 為 MemoComponent 時,會調(diào)用 updateMemoComponent 方法。當(dāng)我們在使用 memo 沒有傳入第二個參數(shù)時,會調(diào)用默認的 shallowEqual 對 prevProps 和 nextProps 進行淺比較,當(dāng)結(jié)果為 true 時,組件就不會重新渲染。

function beginWork(...) {
  // ...
  switch (workInProgress.tag) {
    case MemoComponent: {
      return updateMemoComponent(...);
    }
  }
}

function updateMemoComponent(...) {
  // ...
  if (updateExpirationTime < renderExpirationTime) {
    // Default to shallow comparison
    var compare = Component.compare;
    compare = compare !== null ? compare : shallowEqual;
    if (compare(prevProps, nextProps) && current$$1.ref === workInProgress.ref) {
      return bailoutOnAlreadyFinishedWork(...);
    }
  }
  var newChild = createWorkInProgress(...);
  return newChild;
}

3、注意事項

3.1、當(dāng)組件屬性包含方法和對象時,要結(jié)合useMemo和useCallback一起使用。

???? ????因為函數(shù)組件的特性,當(dāng)組件state或props發(fā)生變化時,其內(nèi)部的變量會重新聲明,如果是引用類型的變量,其引用地址也會發(fā)生改變。所以,像下面的用法,達不到預(yù)期的效果。
錯誤用法:

import { memo, useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  
  const userInfo = { name,age:18 };
  const onClick = () => {};
  
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)}/>
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)}/>
      </label>
      <Greeting userInfo={userInfo} onClick={onClick} />
    </>
  );
}

const Greeting = memo(function Greeting({ userInfo, onClick }) {
  return <h3>Hello{userInfo.name && ', '}{userInfo.name}!</h3>;
});

正確用法:

const userInfo = useMemo(() => {
  return { name, age: 18 };
}, [name]);
const onClick = useCallback(() => {
  // do something
},[]);
// 使用結(jié)合useMemo、useCallback使用
 <Greeting userInfo={userInfo} onClick={onClick} />
// 展開對象 僅適用于對象屬性都是基本類型的情況
 <Greeting {...obj}  />

3.2、路由入口組件不要使用memo

因為memo返回的記憶化組件,通常是作為子組件來使用。當(dāng)頁面加載完成,路由入口組件的props,通常是不會再次變化的,所以使用memo包裹,起不到性能優(yōu)化的效果。

// 路由配置
const routes = [
  {
    path: '/orderList',
    component: '@/pages/orderList',
    title: '訂單列表'
  }
]
// 錯誤示例
const OrderList = () => {
  return (
    <div>....</div>
  )
}
export default memo(OrderList)

4、結(jié)論

???? ????說了這么多,那項目中到底要不要使用 memo 呢?React 官方的回答是,當(dāng)你的代碼沒有 memo 就無法運行時,再去使用。為什么官方不建議在任何地方都使用 memo 呢,我總結(jié)了以下三點原因(個人觀點):
1、大部分場景下,memo 對性能提升的效果不是很明顯。
2、使代碼變得冗余、不易讀。
3、占用更多的內(nèi)存(memo 不會修改原組件,而是返回一個新的、記憶化的組件,需要結(jié)合 useMemo、useCallback 等 Hook 一起使用)。
???? ????當(dāng)我們遇到性能問題時,首先要做的是檢查代碼寫得是否有問題,能不能使用其他手段來優(yōu)化代碼,例如,減少不必要的狀態(tài)提升,而不是直接無腦使用 memo。通常情況下,當(dāng)你的項目有很多細粒度(繪圖、數(shù)據(jù)可視化、拖拽等)的交互時,使用 memo 對性能的提示效果才會更明顯。

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

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

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