[譯] React性能優(yōu)化-虛擬Dom原理淺析

bkg.jpg

本文譯自《Optimizing React: Virtual DOM explained》拳亿,作者是Alexey IvanovAndy Barnov藕甩,來自Evil Martians’ team團隊番刊。

譯者說:通過一些實際場景和demo菊匿,給大家描述React的Virtual Dom Diff一些核心的原理和規(guī)則,以及基于這些我們可以做些什么提高應(yīng)用的性能宛篇,很棒的文章娃磺。


通過學(xué)習(xí)React的Virtual DOM的知識,去加速你們的應(yīng)用吧叫倍。對框架內(nèi)部實現(xiàn)的介紹偷卧,比較全面且適合初學(xué)者,我們會讓JSX更加簡單易懂吆倦,給你展示React是如何判斷要不要重新render听诸,解釋如何找到應(yīng)用的性能瓶頸,以及給大家一些小貼士蚕泽,如何避免常見錯誤晌梨。

React在前端圈內(nèi)保持領(lǐng)先的原因之一,因為它的學(xué)習(xí)曲線非常平易近人:把你的模板包在JSX,了解一下propsstate的概念之后仔蝌,你就可以輕松寫出React代碼了泛领。

如果你已經(jīng)熟悉React的工作方式,可以直接跳至“優(yōu)化我的代碼”篇敛惊。

但要真正掌握React渊鞋,你需要像React一樣思考(think in React)。本文也會試圖在這個方面幫助你瞧挤。

下面看看我們其中一個項目中的React table:

ebay_table.png

這個表里有數(shù)百個動態(tài)(表格內(nèi)容變化)和可過濾的選項篓像,理解這個框架更精細的點,對于保證順暢的用戶體驗至關(guān)重要皿伺。


當(dāng)事情出錯時员辩,你一定能感覺到。輸入字段變得遲緩鸵鸥,復(fù)選框需要檢查一秒鐘奠滑,彈窗一個世紀(jì)后才出現(xiàn),等等妒穴。


為了能夠解決這些問題宋税,我們需要完成一個React組件的整個生命旅程,從一開始的聲明定義到在頁面上渲染(再然后可能會更新)讼油。系好安全帶杰赛,我們要發(fā)車了!

JSX的背后

這個過程一般在前端會稱為“轉(zhuǎn)譯”矮台,但其實“匯編”將是一個更精確的術(shù)語乏屯。

React開發(fā)人員敦促你在編寫組件時使用一種稱為JSX的語法,混合了HTML和JavaScript瘦赫。但瀏覽器對JSX及其語法毫無頭緒辰晕,瀏覽器只能理解純碎的JavaScript,所以JSX必須轉(zhuǎn)換成JavaScript确虱。這里是一個div的JSX代碼含友,它有一個class name和一些內(nèi)容:

<div className='cn'>
  Content!
</div>

以上的代碼,被轉(zhuǎn)換成“正經(jīng)”的JavaScript代碼校辩,其實是一個帶有一些參數(shù)的函數(shù)調(diào)用:

React.createElement(
  'div',
  { className: 'cn' },
  'Content!'
);

讓我們仔細看看這些參數(shù)窘问。

  • 第一個是元素的type。對于HTML標(biāo)簽宜咒,它將是一個帶有標(biāo)簽名稱的字符串惠赫。
  • 第二個參數(shù)是一個包含所有元素屬性(attributes)的對象。如果沒有荧呐,它也可以是空的對象汉形。
  • 剩下的參數(shù)都可以認為是元素的子元素(children)纸镊。元素中的文本也算作一個child,是個字符串'Content概疆!' 作為函數(shù)調(diào)用的第三個參數(shù)放置逗威。

你應(yīng)該可以想象,當(dāng)我們有更多的children時會發(fā)生什么:

<div className='cn'>
  Content 1!
  <br />
  Content 2!
</div>
React.createElement(
  'div',
  { className: 'cn' },
  'Content 1!',              // 1st child
  React.createElement('br'), // 2nd child
  'Content 2!'               // 3rd child
)

我們的函數(shù)現(xiàn)在有五個參數(shù):

  • 一個元素的類型
  • 一個屬性對象
  • 三個子元素岔冀。

因為其中一個child是一個React已知的HTML標(biāo)簽(<br/>)凯旭,所以它也會被描述為一個函數(shù)調(diào)用(React.createElement('br'))。

到目前為止使套,我們已經(jīng)涵蓋了兩種類型的children:

  • 簡單的String
  • 另一種會調(diào)用React.createElement罐呼。

然而,還有其他值可以作為參數(shù):

  • 基本類型 false, null, undefined, true
  • 數(shù)組
  • React Components

可以使用數(shù)組是因為可以將children分組并作為一個參數(shù)傳遞:

React.createElement(
  'div',
  { className: 'cn' },
  ['Content 1!', React.createElement('br'), 'Content 2!']
)

當(dāng)然了侦高,React的厲害之處嫉柴,不僅僅因為我們可以把HTML標(biāo)簽直接放在JSX中使用,而是我們可以自定義自己的組件奉呛,例如:

function Table({ rows }) {
  return (
    <table>
      {rows.map(row => (
        <tr key={row.id}>
          <td>{row.title}</td>
        </tr>
      ))}
    </table>
  );
}

組件可以讓我們把模板分解為多個可重用的塊计螺。在上面的“函數(shù)式”(functional)組件的例子里,我們接收一個包含表格行數(shù)據(jù)的對象數(shù)組瞧壮,最后返回一個調(diào)用React.createElement方法的<table>元素登馒,rows則作為children傳進table。

無論什么時候咆槽,我們這樣去聲明一個組件時:

<Table rows={rows} />

從瀏覽器的角度來看陈轿,我們是這么寫的:

React.createElement(Table, { rows: rows });

注意,這次我們的第一個參數(shù)不是String描述的HTML標(biāo)簽秦忿,而是一個引用麦射,指向我們編寫組件時編寫的函數(shù)。組件的attributes現(xiàn)在是接收的props參數(shù)了小渊。

把組件(components)組合成頁面(a page)

所以法褥,我們已經(jīng)將所有JSX組件轉(zhuǎn)換為純JavaScript,現(xiàn)在我們有一大堆函數(shù)調(diào)用酬屉,它的參數(shù)會被其他函數(shù)調(diào)用的,或者還有更多的其他函數(shù)調(diào)用這些參數(shù)......這些帶參數(shù)的函數(shù)調(diào)用揍愁,是怎么轉(zhuǎn)化成組成這個頁面的實體DOM的呢呐萨?

為此,我們有一個ReactDOM庫及其它的render方法:

function Table({ rows }) { /* ... */ } // defining a component

// rendering a component
ReactDOM.render(
  React.createElement(Table, { rows: rows }), // "creating" a component
  document.getElementById('#root') // inserting it on a page
);

當(dāng)ReactDOM.render被調(diào)用時莽囤,React.createElement最終也會被調(diào)用谬擦,返回以下對象:

// There are more fields, but these are most important to us
{
  type: Table,
  props: {
    rows: rows
  },
  // ...
}

這些對象,在React的角度上朽缎,構(gòu)成了虛擬DOM惨远。


他們將在所有進一步的渲染中相互比較谜悟,并最終轉(zhuǎn)化為 真正的DOM(virtual VS real, 虛擬DOM VS 真實DOM)。

下面是另一個例子:這次div有一個class屬性和幾個children:

React.createElement(
  'div',
  { className: 'cn' },
  'Content 1!',
  'Content 2!',
);

變成:

{
  type: 'div',
  props: {
    className: 'cn',
    children: [
      'Content 1!',
      'Content 2!'
    ]
  }
}

需要注意的是北秽,那些除了typeattribute以外的屬性葡幸,原本是單獨傳進來的,轉(zhuǎn)換之后贺氓,會作為在props.children以一個數(shù)組的形式打包存在蔚叨。也就是說,無論children是作為數(shù)組還是參數(shù)列表傳遞都沒關(guān)系 —— 在生成的虛擬DOM對象的時候辙培,它們最后都會被打包在一起的蔑水。

進一步說,我們可以直接在組件中把children作為一項屬性傳進去扬蕊,結(jié)果還是一樣的:

<div className='cn' children={['Content 1!', 'Content 2!']} />

在構(gòu)建虛擬DOM對象完成之后搀别,ReactDOM.render將會按下面的原則,嘗試將其轉(zhuǎn)換為瀏覽器可以識別和展示的DOM節(jié)點:

  • 如果type包含一個帶有String類型的標(biāo)簽名稱(tag name)—— 創(chuàng)建一個標(biāo)簽尾抑,附帶上props下所有attributes领曼。

  • 如果type是一個函數(shù)(function)或者類(class),調(diào)用它蛮穿,并對結(jié)果遞歸地重復(fù)這個過程庶骄。

  • 如果props下有children屬性 —— 在父節(jié)點下,針對每個child重復(fù)以上過程践磅。

最后单刁,得到以下HTML(對于我們的表格示例):

<table>
  <tr>
    <td>Title</td>
  </tr>
  ...
</table>

重新構(gòu)建DOM(Rebuilding the DOM)

在實際應(yīng)用場景,render通常在根節(jié)點調(diào)用一次府适,后續(xù)的更新會有state來控制和觸發(fā)調(diào)用羔飞。

請注意,標(biāo)題中的“重新”檐春!當(dāng)我們想更新一個頁面而不是全部替換時逻淌,React中的魔法就開始了。我們有一些實現(xiàn)它的方式疟暖。我們先從最簡單的開始 —— 在同一個node節(jié)點再次執(zhí)行ReactDOM.render卡儒。

// Second call
ReactDOM.render(
  React.createElement(Table, { rows: rows }),
  document.getElementById('#root')
);

這一次,上面的代碼的表現(xiàn)俐巴,跟我們已經(jīng)看到的有所不同骨望。React將從頭開始創(chuàng)建所有DOM節(jié)點并將其放在頁面上,而不是從頭開始創(chuàng)建所有DOM節(jié)點欣舵,React將啟動其diff算法擎鸠,來確定節(jié)點樹的哪些部分必須更新,哪些可以保持不變缘圈。

那么劣光,它是怎樣工作的呢袜蚕?其實只有少數(shù)幾個簡單的場景,理解它們將對我們的優(yōu)化幫助很大绢涡。請記住牲剃,現(xiàn)在我們在看的,是在React Virtual DOM里面用來代表節(jié)點的對象垂寥。

場景1:type是一個字符串颠黎,type在通話中保持不變,props也沒有改變滞项。

// before update
{ type: 'div', props: { className: 'cn' } }

// after update
{ type: 'div', props: { className: 'cn' } }

這是最簡單的情況:DOM保持不變狭归。

場景2:type仍然是相同的字符串,props是不同的文判。

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'div', props: { className: 'cnn' } }

type仍然代表HTML元素过椎,React知道如何通過標(biāo)準(zhǔn)DOM API調(diào)用來更改元素的屬性,而無需從DOM樹中刪除一個節(jié)點戏仓。

場景3:type已更改為不同的String或從String組件疚宇。

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'span', props: { className: 'cn' } }

React看到的type是不同的,它甚至不會嘗試更新我們的節(jié)點:old元素將和它的所有子節(jié)點一起被刪除(unmounted卸載)赏殃。因此敷待,將元素替換為完全不同于DOM樹的東西代價會非常昂貴。幸運的是仁热,這在現(xiàn)實世界中很少發(fā)生榜揖。

劃重點,記住React使用===(triple equals)來比較type的值抗蠢,所以這兩個值需要是相同類或相同函數(shù)的相同實例举哟。

下一個場景更加有趣,通常我們會這么使用React迅矛。

場景4:type是一個component妨猩。

// before update:
{ type: Table, props: { rows: rows } }

// after update:
{ type: Table, props: { rows: rows } }

你可能會說,“咦秽褒,但沒有任何變化昂琛!”震嫉,但是你錯了森瘪。


如果type是對函數(shù)或類的引用(即常規(guī)的React組件),并且我們啟動了tree diff的過程票堵,則React會持續(xù)地去檢查組件的內(nèi)部邏輯,以確保render返回的值不會改變(類似對副作用的預(yù)防措施)逮栅。對樹中的每個組件進行遍歷和掃描 —— 是的悴势,在復(fù)雜的渲染場景下窗宇,成本可能會非常昂貴!

值得注意的是特纤,一個componentrender(只有類組件在聲明時有這個函數(shù))跟ReactDom.render不是同一個函數(shù)军俊。

關(guān)注子組件(children)的情況

除了上述四種常見場景之外,當(dāng)一個元素有多個子元素時捧存,我們還需要考慮React的行為》喙現(xiàn)在假設(shè)我們有這么一個元素:

// ...
props: {
  children: [
      { type: 'div' },
      { type: 'span' },
      { type: 'br' }
  ]
},
// ...

我們想要交換一下這些children的順序:

// ...
props: {
  children: [
    { type: 'span' },
    { type: 'div' },
    { type: 'br' }
  ]
},
// ...

之后會發(fā)生什么呢?

當(dāng)diffing的時候昔穴,如果React在檢查props.children下的數(shù)組時镰官,按順序去對比數(shù)組內(nèi)元素的話:index 0將與index 0進行比較,index 1和index 1吗货,等等泳唠。對于每一次對比,React會使用之前提過的diff規(guī)則宙搬。在我們的例子里笨腥,它認為div成為一個span,那么就會運用到情景3勇垛。這樣不是很有效率的:想象一下脖母,我們已經(jīng)從1000行中刪除了第一行。React將不得不“更新”剩余的999個子項闲孤,因為按index去對比的話谆级,內(nèi)容從第一條開始就不相同了。

幸運的是崭放,React有一個內(nèi)置的方法(built-in)來解決這個問題哨苛。如果一個元素有一個key屬性,那么元素將按key而不是index來比較币砂。只要key是唯一的建峭,React就會移動元素,而不是將它們從DOM樹中移除然后再將它們放回(這個過程在React里叫mounting和unmounting)决摧。

// ...
props: {
  children: [ // Now React will look on key, not index
    { type: 'div', key: 'div' },
    { type: 'span', key: 'span' },
    { type: 'br', key: 'bt' }
  ]
},
// ...

當(dāng)state發(fā)生了改變

到目前為止亿蒸,我們只聊了下React哲學(xué)里面的props部分,卻忽視了另外很重要的一部分state掌桩。下面是一個簡單的stateful組件:

class App extends Component {
  state = { counter: 0 }

  increment = () => this.setState({
    counter: this.state.counter + 1,
  })

  render = () => (<button onClick={this.increment}>
    {'Counter: ' + this.state.counter}
  </button>)
}

state對象里边锁,我們有一個keycounter。點擊按鈕時波岛,這個值會增加茅坛,然后按鈕的文本也會發(fā)生相應(yīng)的改變。但是则拷,當(dāng)我們這樣做時贡蓖,DOM中發(fā)生了什么曹鸠?哪部分將被重新計算和更新?

調(diào)用this.setState會導(dǎo)致re-render(重新渲染)斥铺,但不會影響到整個頁面彻桃,而只會影響組件本身及其children組件。父母和兄弟姐妹都不會受到影響晾蜘。當(dāng)我們有一個層級很深的組件鏈時邻眷,這會讓狀態(tài)更新變得非常方便,因為我們只需要重繪(redraw)它的一部分剔交。

把問題說清楚

我們準(zhǔn)備了一個小demo肆饶,以便你可以在看到在“野蠻生長”的React編碼方式下最常見的問題,后續(xù)我也告訴大家怎么去解決這些問題省容。你可以在這里看看它的源代碼抖拴。你還需要React Developer Tools,請確保瀏覽器安裝了它們腥椒。

我們首先要看看的是阿宅,哪些元素以及什么時候?qū)е耉irtual DOM的更新。在瀏覽器的開發(fā)工具中笼蛛,打開React面板并選擇“Highlight Updates”復(fù)選框:

react_dev_tools.png

現(xiàn)在嘗試在表格中添加一行洒放。如你所見,頁面上的每個元素周圍都會顯示一個邊框滨砍。這意味著每次添加一行時往湿,React都在計算和比較整個虛擬DOM樹。現(xiàn)在嘗試點擊一行內(nèi)的counter按鈕惋戏。你將看到state更新后虛擬DOM如何更新 —— 只有引用了state key的元素及其children受到影響领追。

React DevTools會提示問題出在哪里,但不會告訴我們有關(guān)細節(jié)的信息:特別是所涉及的更新响逢,是由diffing元素引起的绒窑?還是被掛載(mounting)或者被卸載(unmounting)了?要了解更多信息舔亭,我們需要使用React的內(nèi)置分析器(注意它不適用于生產(chǎn)模式)些膨。

添加?react_perf到應(yīng)用的URL,然后轉(zhuǎn)到Chrome DevTools中的“Performance”標(biāo)簽钦铺。點擊“錄制”(Record)并在表格上點擊订雾。添加一些row,更改一下counter矛洞,然后點擊“停止”(Stop)洼哎。

react_perf_tools.png

在輸出的結(jié)果中,我們關(guān)注“User timing”這項指標(biāo)。放大時間軸直到看到“React Tree Reconciliation”這個組及其子項谱净。這些就是我們組件的名稱窑邦,它們旁邊都寫著[update]或[mount]擅威。


我們的大部分性能問題都屬于這兩類問題之一壕探。


無論是組件(還是從它分支的其他組件)出于某種原因都會在每次更新時re-mounted(慢),又或者我們在大型應(yīng)用上執(zhí)行對每個分支做diff郊丛,盡管這些組件并沒有發(fā)生改變李请,我們不希望這些情況的發(fā)生。

優(yōu)化我們的代碼:Mounting / Unmounting

現(xiàn)在厉熟,我們已經(jīng)了解到當(dāng)需要update Virtual Dom時导盅,React是依據(jù)哪些規(guī)則去判斷要不要更新,以及也知道了我們可以通過什么方式去追蹤這些diff場景的背后發(fā)生了什么揍瑟,我們終于準(zhǔn)備好優(yōu)化我們的代碼了白翻!首先,我們來看看mounts/unmounts绢片。

如果你能夠注意到當(dāng)一個元素包含的多個children滤馍,他們是由array組成的話,你可以實現(xiàn)十分顯著的速度優(yōu)化底循。

我們來看看這個case:

<div>
  <Message />
  <Table />
  <Footer />
</div>

在我們的Virtual DOM里這么表示:

// ...
props: {
  children: [
    { type: Message },
    { type: Table },
    { type: Footer }
  ]
}
// ...

這里有一個簡單的Message例子巢株,就是一個div寫著一些簡單的文本,和以及一個巨大的Table熙涤,比方說阁苞,超過1000行。它們(MessageTable)都是頂級div的子組件祠挫,所以它們被放置在父節(jié)點的props.children下那槽,并且它們key都不會有。React甚至不會通過控制臺警告我們要給每個child分配key等舔,因為children正在React.createElement作為參數(shù)列表傳遞給父元素骚灸,而不是直接遍歷一個數(shù)組。

現(xiàn)在我們的用戶已讀了一個通知软瞎,Message(譬如新通知按鈕)從DOM上移除逢唤。TableFooter是剩下的全部。

// ...
props: {
  children: [
    { type: Table },
    { type: Footer }
  ]
}
// ...

React會怎么處理呢涤浇?它會看作是一個array類型的children鳖藕,現(xiàn)在少了第一項,從前第一項是Message現(xiàn)在是Table了只锭,也沒有key作為索引著恩,比較type的時候又發(fā)現(xiàn)它們倆不是同一個function或者class的同一個實例,于是會把整個Tableunmount,然后在mount回去喉誊,渲染它的1000+行子數(shù)據(jù)邀摆。

因此,你可以給每個component添加唯一的key(但在目特殊的case下伍茄,使用key并不是最佳選擇)栋盹,或者采用更聰明的小技巧:使用短路求值(又名“最小化求值”),這是JavaScript和許多其他現(xiàn)代語言的特性敷矫±瘢看:

// Using a boolean trick
<div>
  {isShown && <Message />}
  <Table />
  <Footer />
</div>

雖然Message會離開屏幕,父元素divprops.children仍然會擁有三個元素曹仗,children[0]具有一個值false(一個布爾值)榨汤。請記住true, false, null, undefined是虛擬DOM對象type屬性的允許值,我們最終得到了類似的結(jié)果:

// ...
props: {
  children: [
    false, //  isShown && <Message /> evaluates to false
    { type: Table },
    { type: Footer }
  ]
}
// ...

因此怎茫,有沒有Message組件收壕,我們的索引值都不會改變,Table當(dāng)然仍然會跟Table比較(當(dāng)type是一個函數(shù)或類的引用時轨蛤,diff比較的成本還是會有的)蜜宪,但僅僅比較虛擬DOM的成本,通常比“刪除DOM節(jié)點”并“從0開始創(chuàng)建”它們要來得快俱萍。

現(xiàn)在我們來看看更多的東西端壳。大家都挺喜歡用HOC的,高階組件是一個將組件作為參數(shù)枪蘑,執(zhí)行某些操作损谦,最后返回另外一個不同功能的組件:

function withName(SomeComponent) {
  // Computing name, possibly expensive...
  return function(props) {
    return <SomeComponent {...props} name={name} />;
  }
}

這是一種常見的模式,但你需要小心岳颇。如果我們這么寫:

class App extends React.Component() {
  render() {
    // Creates a new instance on each render
    const ComponentWithName = withName(SomeComponent);
    return <SomeComponentWithName />;
  }
}

我們在父節(jié)點的render方法內(nèi)部創(chuàng)建一個HOC照捡。當(dāng)我們重新渲染(re-render)樹時,虛擬DOM是這樣子的:

// On first render:
{
  type: ComponentWithName,
  props: {},
}

// On second render:
{
  type: ComponentWithName, // Same name, but different instance
  props: {},
}

現(xiàn)在话侧,React會對ComponentWithName這個實例做diff栗精,但由于此時同名引用了不同的實例,因此全等比較(triple equal)失敗瞻鹏,一個完整的re-mount會發(fā)生(整個節(jié)點換掉)悲立,而不是調(diào)整屬性值或順序。注意它也會導(dǎo)致狀態(tài)丟失新博,如此處所述薪夕。幸運的是,這很容易解決赫悄,你需要始終在render外面創(chuàng)建一個HOC:

// Creates a new instance just once
const ComponentWithName = withName(Component);

class App extends React.Component() {
  render() {
    return <ComponentWithName />;
  }
}

優(yōu)化我的代碼:Updating

現(xiàn)在我們可以確保在非必要的時候原献,不做re-mount的事情了馏慨。然而,對位于DOM樹根部附近(層級越上面的元素)的組件所做的任何更改都會導(dǎo)致其所有children的diffing和調(diào)整(reconciliation)姑隅。在層級很多写隶、結(jié)構(gòu)復(fù)雜的應(yīng)用里,這些成本很昂貴讲仰,但經(jīng)常是可以避免的慕趴。


如果有一種方法可以告訴React你不用來檢查這個分支了,因為我們可以肯定那個分支不會有更新叮盘,那就太棒了秩贰!


這種方式是真的有的哈,它涉及一個built-in方法叫shouldComponentUpdate柔吼,它也是組件生命周期的一部分。這個方法的調(diào)用時機:組件的render和組件接收到state或props的值的更新時丙唧。然后我們可以自由地將它們與我們當(dāng)前的值進行比較愈魏,并決定是否更新我們的組件(返回truefalse)。如果我們返回false想际,React將不會重新渲染組件培漏,也不會檢查它的所有子組件。

通常來說胡本,比較兩個集合(set)propsstate一個簡單的淺層比較(shallow comparison)就足夠了:如果頂層的值不同牌柄,我們不必接著比較了。淺比較不是JavaScript的一個特性侧甫,但有很多小而美的庫utilities)可以讓我們用上那么棒的功能珊佣。

現(xiàn)在可以像這樣編寫我們的代碼:

class TableRow extends React.Component {

  // will return true if new props/state are different from old ones
  shouldComponentUpdate(nextProps, nextState) {
    const { props, state } = this;
    return !shallowequal(props, nextProps)
           && !shallowequal(state, nextState);
  }

  render() { /* ... */ }
}

但是你甚至都不需要自己寫代碼,因為React把這個特性內(nèi)置在一個類React.PureComponent里面披粟。它類似于 React.Component咒锻,只是shouldComponentUpdate已經(jīng)為你實施了一個淺的props/state比較。

這聽起來很“不動腦”守屉,在聲明class繼承(extends)的時候惑艇,把Component換成PureComponent就可以享受高效率。事實上拇泛,并不是這么“傻瓜”滨巴,看看這些例子:

<Table
    // map returns a new instance of array so shallow comparison will fail
    rows={rows.map(/* ... */)}
    // object literal is always "different" from predecessor
    style={ { color: 'red' } }
    // arrow function is a new unnamed thing in the scope, so there will always be a full diffing
    onUpdate={() => { /* ... */ }}
/>

上面的代碼片段演示了三種最常見的反模式。盡量避免它們俺叭!


如果你能注意點恭取,在render定義之外創(chuàng)建所有對象、數(shù)組和函數(shù)绪颖,并確保它們在各種調(diào)用間秽荤,不發(fā)生更改 —— 你是安全的甜奄。


你在updated demo,所有table的rows都被“凈化”(purified)過窃款,你可以看到PureComponent的表現(xiàn)了课兄。如果你在React DevTools中打開“Highlight Updates”,你會注意到只有表格本身和新行在插入時會觸發(fā)render晨继,其他的行保持不變烟阐。

[譯者說:為了便于大家理解purified,譯者在下面插入了原文demo的一段代碼]

class TableRow extends React.PureComponent {
  render() {
    return React.createElement('tr', { className: 'row' },
      React.createElement('td', { className: 'cell' }, this.props.title),
      React.createElement('td', { className: 'cell' }, React.createElement(Button)),
    );
  }
};

不過紊扬,如果你迫不及待地all in PureComponent蜒茄,在應(yīng)用里到處都用的話 —— 控制住你自己!

shallow比較兩組propsstate不是免費的餐屎,對于大多數(shù)基本組件來說檀葛,甚至都不值得:shallowComparediffing算法需要耗費更多的時間。

使用這個經(jīng)驗法則:pure component適用于復(fù)雜的表單和表格腹缩,但它們通常會減慢簡單元素(按鈕屿聋、圖標(biāo))的效率。


感謝你的閱讀藏鹊!現(xiàn)在你已準(zhǔn)備好將這些見解應(yīng)用到你的應(yīng)用程序中润讥。可以使用我們的小demo(用了沒有用PureComponent)的倉庫作為你的實驗的起點盘寡。此外楚殿,請繼續(xù)關(guān)注本系列的下一部分,我們計劃涵蓋Redux并優(yōu)化你的數(shù)據(jù)竿痰,目標(biāo)是提高整個應(yīng)用的總體性能脆粥。

譯者說

正如原文末所說,Alex和Andy后續(xù)會繼續(xù)寫一個關(guān)于整體性能的系列菇曲,包括核心React和Redux等冠绢,我也會繼續(xù)跟蹤這個系列的文章,到時po到我的個人博客和知乎專欄《集異璧》常潮,感興趣的同學(xué)們可以關(guān)注一下哈 :)

歡迎對本文的翻譯質(zhì)量弟胀、內(nèi)容的各種討論。若有表述不當(dāng)喊式,歡迎斧正擦俐。

2018.05.13坞生,晴略荡,杭州濱江
Yuying Wu


筆者 @Yuying Wu箕慧,前端愛好者 / 鼓勵師 / 新西蘭打工度假 / 鏟屎官。目前就職于某大型電商的B2B前端團隊献联。

感謝你讀到這里竖配。如果你和我一樣喜歡前端何址,喜歡搗騰獨立博客或者前沿技術(shù),或者有什么職業(yè)疑問进胯,歡迎關(guān)注我以及各種交流哈用爪。

獨立博客:wuyuying.com
知乎ID:@Yuying Wu
Github:Yuying Wu

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胁镐,隨后出現(xiàn)的幾起案子偎血,更是在濱河造成了極大的恐慌,老刑警劉巖盯漂,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颇玷,死亡現(xiàn)場離奇詭異,居然都是意外死亡就缆,警方通過查閱死者的電腦和手機帖渠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來违崇,“玉大人阿弃,你說我怎么就攤上這事⌒哐樱” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵脾还,是天一觀的道長伴箩。 經(jīng)常有香客問我,道長鄙漏,這世上最難降的妖魔是什么嗤谚? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮怔蚌,結(jié)果婚禮上巩步,老公的妹妹穿的比我還像新娘。我一直安慰自己桦踊,他們只是感情好椅野,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著籍胯,像睡著了一般竟闪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杖狼,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天炼蛤,我揣著相機與錄音,去河邊找鬼蝶涩。 笑死理朋,一個胖子當(dāng)著我的面吹牛絮识,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嗽上,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼次舌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炸裆?” 一聲冷哼從身側(cè)響起垃它,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烹看,沒想到半個月后国拇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡惯殊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年酱吝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片土思。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡务热,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出己儒,到底是詐尸還是另有隱情崎岂,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布闪湾,位于F島的核電站冲甘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏途样。R本人自食惡果不足惜江醇,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望何暇。 院中可真熱鬧陶夜,春花似錦、人聲如沸裆站。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遏插。三九已至捂贿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胳嘲,已是汗流浹背厂僧。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留了牛,地道東北人颜屠。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓辰妙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親甫窟。 傳聞我的和親對象是個殘疾皇子密浑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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

  • It's a common pattern in React to wrap a component in an ...
    jplyue閱讀 3,275評論 0 2
  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過程中的一些閱讀筆記粗井,個人覺得該教程講解深入淺出尔破,比目前大...
    leonaxiong閱讀 2,842評論 1 18
  • 學(xué)習(xí)如何在Flow中使用React 將Flow類型添加到React組件后,F(xiàn)low將靜態(tài)地確保你按照組件被設(shè)計的方...
    vincent_z閱讀 6,362評論 4 21
  • 以下內(nèi)容是我在學(xué)習(xí)和研究React時浇衬,對React的特性懒构、重點和注意事項的提取、精練和總結(jié)耘擂,可以做為React特性...
    科研者閱讀 8,242評論 2 21
  • ?由于css的引入方式有3種胆剧,在使用的時候就難免要進行選擇。這里將分析一下它們各自的優(yōu)缺點醉冤,方便大家選擇秩霍。 行間樣...
    初級程序員閱讀 3,814評論 3 8