本文譯自《Optimizing React: Virtual DOM explained》拳亿,作者是Alexey Ivanov和Andy 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
,了解一下props
和state
的概念之后仔蝌,你就可以輕松寫出React代碼了泛领。
如果你已經(jīng)熟悉React的工作方式,可以直接跳至“優(yōu)化我的代碼”篇敛惊。
但要真正掌握React渊鞋,你需要像React一樣思考(think in React)。本文也會試圖在這個方面幫助你瞧挤。
下面看看我們其中一個項目中的React table:
這個表里有數(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!'
]
}
}
需要注意的是北秽,那些除了type
和attribute
以外的屬性葡幸,原本是單獨傳進來的,轉(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ù)雜的渲染場景下窗宇,成本可能會非常昂貴!
值得注意的是特纤,一個component
的render
(只有類組件在聲明時有這個函數(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ù)選框:
現(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)洼哎。
在輸出的結(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行。它們(Message
和Table
)都是頂級div
的子組件祠挫,所以它們被放置在父節(jié)點的props.children
下那槽,并且它們key
都不會有。React甚至不會通過控制臺警告我們要給每個child
分配key
等舔,因為children正在React.createElement
作為參數(shù)列表傳遞給父元素骚灸,而不是直接遍歷一個數(shù)組。
現(xiàn)在我們的用戶已讀了一個通知软瞎,Message
(譬如新通知按鈕)從DOM上移除逢唤。Table
和Footer
是剩下的全部。
// ...
props: {
children: [
{ type: Table },
{ type: Footer }
]
}
// ...
React會怎么處理呢涤浇?它會看作是一個array類型的children鳖藕,現(xiàn)在少了第一項,從前第一項是Message
現(xiàn)在是Table
了只锭,也沒有key
作為索引著恩,比較type
的時候又發(fā)現(xiàn)它們倆不是同一個function或者class的同一個實例,于是會把整個Table
unmount,然后在mount回去喉誊,渲染它的1000+行子數(shù)據(jù)邀摆。
因此,你可以給每個component添加唯一的key
(但在目特殊的case下伍茄,使用key并不是最佳選擇)栋盹,或者采用更聰明的小技巧:使用短路求值(又名“最小化求值”),這是JavaScript和許多其他現(xiàn)代語言的特性敷矫±瘢看:
// Using a boolean trick
<div>
{isShown && <Message />}
<Table />
<Footer />
</div>
雖然Message
會離開屏幕,父元素div
的props.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)前的值進行比較愈魏,并決定是否更新我們的組件(返回true
或false
)。如果我們返回false
想际,React將不會重新渲染組件培漏,也不會檢查它的所有子組件。
通常來說胡本,比較兩個集合(set)props
和state
一個簡單的淺層比較(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比較兩組props
和state
不是免費的餐屎,對于大多數(shù)基本組件來說檀葛,甚至都不值得:shallowCompare
比diffing
算法需要耗費更多的時間。
使用這個經(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