Component/PureComponent/React.memo()

1. Component 和 PureComponent

react 包暴露出來的其中兩個基礎(chǔ)的 class(源碼中是以構(gòu)造函數(shù)的形式):ComponentPureComponent

App extends React.Component {...} 用的爐火純青,但后者很少用妻顶。

React 生命周期函數(shù)中有一個 shouldComponentUpdate(nextProps, nextState) 譬淳, 此方法缺省時總是返回 true沉桌,即只要當前組件調(diào)用 this.setState()疫铜,或者父組件產(chǎn)生更新压彭,那么當前組件總是會更新。

這可能有些性能浪費菌羽,所以你可以主動配置該生命周期函數(shù)掠械,判斷當 propsstate 真的發(fā)生了變化,才返回 true注祖。

或者使用 PureComponent猾蒂,其源碼中有句注釋:

Convenience component with default shallow equality check for sCU.

它實現(xiàn)了默認的淺比較方法,即:shouldComponentUpdate(nextProps, nextState) 方法是晨。

另外可以是使用 this.forceUpdate() 實現(xiàn)無論如何肚菠,強制更新。

[圖片上傳失敗...(image-d331d4-1575350157935)]

簡單看一下源碼:

/**
 * Base class helpers for the updating state of a component.
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

/*
 * @param {object|function} partialState Next partial state or function to
 *        produce next partial state to be merged with current state.
 * @param {?function} callback Called after state is updated.
 * @final
 * @protected
 */
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

/*
 * @param {?function} callback Called after update is complete.
 * @final
 * @protected
 */
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

/**
 * Convenience component with default shallow equality check for sCU.
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

源碼中可以看出兩個“類” 本身定義時沒多大區(qū)別罩缴,PureComponent 只是繼承了 Component蚊逢,并多設(shè)置了一個屬性 pureComponentPrototype.isPureReactComponent = truePureComponent 這個標記就很有用了靴庆,會在別處被用來指導是否進行淺比較,從而決定是否更新怒医,props 或者 state 發(fā)生變化后炉抒,就應(yīng)該更新:

if (type.prototype && type.prototype.isPureReactComponent) {
  shouldUpdate =
    !shallowEqual(oldProps, props) || !shallowEqual(oldState, state);
}

3. React.memo

3.1 基本用法

React.memo() 是 React v16.6 中引入的新功能,和 React.PureComponent 類似稚叹,可以用于函數(shù)組件的渲染焰薄,第一個參數(shù)是函數(shù)組件拿诸,第二個可選參數(shù)是自定義的判斷函數(shù):

React.memo(Component, [areEqual(prevProps, nextProps)]);
export function Movie({ title, releaseDate }) {
  return (
    <div>
      <div>Movie title: {title}</div>
      <div>Release date: {releaseDate}</div>
    </div>
  );
}

export const MemoizedMovie = React.memo(Movie);

// 第二個可選參數(shù)
function moviePropsAreEqual(prevMovie, nextMovie) {
  return prevMovie.title === nextMovie.title
    && prevMovie.releaseDate === nextMovie.releaseDate;
}

const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);

props 和 state 變化后,react 默認總是執(zhí)行 render塞茅,當然亩码,即使得到了新的虛擬 DOM,也會和舊的 DOM 做 diff 對比野瘦,只有產(chǎn)生了實質(zhì)變化描沟,才更新真實 DOM。而使用本文介紹的 React.PureComponentReact.memo()鞭光,則是為了在一開始就試圖跳過組件的重新渲染吏廉,自然也更不會再對比 虛擬 DOM 了。

3.2 使用場景

React.memo 使用場景是組件頻繁 re-render惰许,且每次接收的 props 經(jīng)常是相同的值席覆。

3.3 注意事項

避免被 callback 破壞初衷

使用 React.memo 另外要注意如果 props 中有父組件傳來的 callback,則應(yīng)保證傳入的 callback 每次是相同的實例汹买,反面教材:

function MyApp({ store, cookies }) {
  return (
    <div className="main">
      <header>
        <MemoizedLogout
          username={store.username}
          onLogout={() => cookies.clear('session')}
        />
      </header>
      {store.content}
    </div>
  );
}

function Logout({ username, onLogout }) {
  return (
    <div onClick={onLogout}>
      Logout {username}
    </div>
  );
}

const MemoizedLogout = React.memo(Logout);

可以在父組件中使用 useCallback() 來保存 callback佩伤,這樣即使多次 render 也是最初的 callback 實例:

function MyApp({ store, cookies }) {
  const onLogout = useCallback(
    () => cookies.clear('session'), 
    [cookies]
  );
  return (
    <div className="main">
      <header>
        <MemoizedLogout
          username={store.username}
          onLogout={onLogout}
        />
      </header>
      {store.content}
    </div>
  );
}

useState()

使用 useState() 時若 state 改變,react 總是會保證 re-render 組件晦毙,即使該組件使用了 React.memo()生巡。

3.4 尾記

對 react 的這些優(yōu)化究竟真的變快了,還是沒什么區(qū)別结序,亦或者強行優(yōu)化反而“反模式”障斋,這就需要一定的衡量手法和指標:profiling


參考:

  1. https://dmitripavlutin.com/use-react-memo-wisely/
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市徐鹤,隨后出現(xiàn)的幾起案子垃环,更是在濱河造成了極大的恐慌,老刑警劉巖返敬,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遂庄,死亡現(xiàn)場離奇詭異,居然都是意外死亡劲赠,警方通過查閱死者的電腦和手機涛目,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凛澎,“玉大人霹肝,你說我怎么就攤上這事∷芗澹” “怎么了沫换?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長最铁。 經(jīng)常有香客問我讯赏,道長垮兑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任漱挎,我火速辦了婚禮系枪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘磕谅。我一直安慰自己私爷,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布怜庸。 她就那樣靜靜地躺著当犯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪割疾。 梳的紋絲不亂的頭發(fā)上嚎卫,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音宏榕,去河邊找鬼拓诸。 笑死,一個胖子當著我的面吹牛麻昼,可吹牛的內(nèi)容都是我干的奠支。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼抚芦,長吁一口氣:“原來是場噩夢啊……” “哼倍谜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叉抡,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤尔崔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后褥民,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體季春,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年消返,在試婚紗的時候發(fā)現(xiàn)自己被綠了载弄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡撵颊,死狀恐怖宇攻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情倡勇,我是刑警寧澤逞刷,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響亲桥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜固耘,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一题篷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧厅目,春花似錦番枚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拗馒,卻和暖如春路星,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诱桂。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工洋丐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挥等。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓友绝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肝劲。 傳聞我的和親對象是個殘疾皇子迁客,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 3. JSX JSX是對JavaScript語言的一個擴展語法, 用于生產(chǎn)React“元素”辞槐,建議在描述UI的時候...
    pixels閱讀 2,810評論 0 24
  • 40掷漱、React 什么是React?React 是一個用于構(gòu)建用戶界面的框架(采用的是MVC模式):集中處理VIE...
    萌妹撒閱讀 1,005評論 0 1
  • 作為一個合格的開發(fā)者催蝗,不要只滿足于編寫了可以運行的代碼切威。而要了解代碼背后的工作原理;不要只滿足于自己的程序...
    六個周閱讀 8,428評論 1 33
  • 今天的React題沒有太多的故事…… 半個月前出了248個Vue的知識點丙号,受到很多朋友的關(guān)注先朦,都強烈要求再出多些R...
    浪子神劍閱讀 10,070評論 6 106
  • It's a common pattern in React to wrap a component in an ...
    jplyue閱讀 3,260評論 0 2