React版本:15.4.2
**翻譯:xiyoki **
React提供了一個(gè)聲明式API怔软,所以你不必?fù)?dān)心每次更新的確切更改近刘。這使得編寫應(yīng)用程序更容易娘荡,但是在React中更新是如何實(shí)現(xiàn)的可能不明顯钞护。本文解釋了我們?cè)赗eact的‘diffing’算法中做出的選擇,以便組件更新是可預(yù)測(cè)养渴,同時(shí)高性能應(yīng)用程序也足夠快雷绢。
Motivation
當(dāng)你使用React時(shí),在單個(gè)時(shí)間點(diǎn)理卑,你可以將該render()
函數(shù)想象為創(chuàng)建一個(gè)React元素樹翘紊。在下一個(gè)state或props更新時(shí),該render()
函數(shù)將返回一個(gè)不同的React元素樹藐唠。React然后需要找出如何有效地更新UI以匹配最新的樹帆疟。
對(duì)于生成將一個(gè)樹變換成另一個(gè)樹的最小操作數(shù)的算法問題,存在一些通用解決方案宇立。然而踪宠, state of the art algorithms具有大約 O(n3)的復(fù)雜性,其中n是樹中的元素?cái)?shù)量妈嘹。
如果我們?cè)赗eact中使用它柳琢,顯示1000個(gè)元素將需要大約十億此比較。這太貴了润脸。相反柬脸,React基于兩個(gè)假設(shè)實(shí)現(xiàn)啟發(fā)式O(n)算法:
- 不同類型的兩個(gè)元素將產(chǎn)生不同的樹。
- 開發(fā)者可以暗示毙驯,在具有key prop的不同渲染之間倒堕,哪些子元素是穩(wěn)定的。
在實(shí)踐中尔苦,這些假設(shè)對(duì)于幾乎所有實(shí)際使用情況都是有效的。
The Diffing Algorithm(差分算法)
當(dāng)差分兩棵樹時(shí),React首先比較兩個(gè)根元素允坚。根據(jù)根元素的類型魂那,行為是不同的。
Elements Of Different Type(不同類型的元素)
每當(dāng)根元素具有不同類型時(shí)稠项,React將拆除舊樹并從頭開始構(gòu)建新樹涯雅。從<a>
到 <img>
, 或從<Article>
到<Comment>
, 或從<Button>
到<div>
- 任何這些將導(dǎo)致完全重建。
當(dāng)拆除樹時(shí)展运,舊的DOM節(jié)點(diǎn)被銷毀活逆。組件實(shí)例接收componentWillUnmount()
。當(dāng)構(gòu)建新樹時(shí)拗胜,將新的DOM節(jié)點(diǎn)插入到DOM中蔗候。組件實(shí)例接收componentWillMount()
,然后componentDidMount()
埂软。與舊樹相關(guān)聯(lián)的任何狀態(tài)都將丟失锈遥。
根下面的任何組件也將被卸載并且它們的狀態(tài)被銷毀。例如勘畔,當(dāng)差分時(shí):
<div>
<Counter />
</div>
<span>
<Counter />
</span>
這將破壞舊的Counter
所灸,并重新安裝一個(gè)新的。
DOM Elements Of The Same Type(相同類型的DOM元素)
當(dāng)比較相同類型的兩個(gè)React DOM元素時(shí)炫七,React會(huì)查看兩者的屬性爬立,保留相同的底層DOM節(jié)點(diǎn),并僅更新更改的屬性万哪。例如:
<div className="before" title="stuff" />
<div className="after" title="stuff" />
通過比較這兩個(gè)元素侠驯,React知道只修改底層DOM節(jié)點(diǎn)上的className
。
更新style
時(shí)壤圃,React也知道只更新已更改的屬性陵霉。例如:
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
當(dāng)在這兩個(gè)元素之間轉(zhuǎn)換時(shí),React知道只修改color
樣式伍绳,而不是修改fontWeight
樣式踊挠。
處理DOM節(jié)點(diǎn)后,React然后對(duì)子節(jié)點(diǎn)進(jìn)行遞歸冲杀。
Component Elements Of The Same Type(相同類型的組件元素)
當(dāng)組件更新時(shí)效床,實(shí)例保持不變,so that state is maintained across renders权谁。React更新底層組件實(shí)例的props以匹配新元素剩檀,并且在底層實(shí)例上調(diào)用 componentWillReceiveProps()
和 componentWillUpdate()
。
接下來旺芽,render()
方法被調(diào)用沪猴,diff算法對(duì)前一個(gè)結(jié)果和新結(jié)果進(jìn)行遞歸辐啄。
Recursing On Children(在子元素上遞歸)
默認(rèn)情況下,當(dāng)對(duì)DOM節(jié)點(diǎn)的子節(jié)點(diǎn)進(jìn)行遞歸時(shí)运嗜,React只是同時(shí)迭代這兩個(gè)字節(jié)點(diǎn)列表壶辜,并在有差異時(shí)生成一個(gè)變量。
例如担租,當(dāng)在子元素的末尾添加一個(gè)元素時(shí)砸民,這兩個(gè)數(shù)之間的轉(zhuǎn)換效果很好:
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React將匹配兩棵<li>first</li>
樹,匹配兩棵<li>second</li>
樹奋救,然后插入<li>third</li>
樹岭参。
如果你天真地在開始處插入一個(gè)元素,那么性能會(huì)更差尝艘。例如演侯,這兩棵樹之間的轉(zhuǎn)換效果不佳:
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React將改變每個(gè)child,而不是意識(shí)到它課可以保持<li>Duke</li>
和<li>Villanova</li>
子樹完好無損利耍。這種低效率會(huì)是一個(gè)問題蚌本。
Keys
為了解決這個(gè)問題,React支持一個(gè)key
屬性隘梨。當(dāng)child有key屬性時(shí)程癌,React使用key將原始樹中的child與后續(xù)樹中的child進(jìn)行匹配。例如轴猎,添加key
到上面低效的示例可以使樹有效地轉(zhuǎn)換:
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
現(xiàn)在React知道key為‘2014’的元素是新的嵌莉,而key為'2015'和‘2016’的元素只是移動(dòng)了一下。
在實(shí)踐中捻脖,尋找key通常不難锐峭。你要顯示的元素可能已具有唯一的ID,因此key可以來自你的數(shù)據(jù):
<li key={item.id}>{item.name}</li>
如果不是這樣可婶,你可以向模型中添加一個(gè)新的ID屬性沿癞,或hash內(nèi)容的某些部分以生成key。
key只需在其兄弟之間是唯一的矛渴,而不是全局唯一的椎扬。
作為最后一種手段,你可以將數(shù)組中的項(xiàng)目索引作為key具温。這可以很好地工作蚕涤,如果項(xiàng)目從來沒有重新排序,但重新排序會(huì)很慢铣猩。
Tradeoffs(權(quán)衡)
重點(diǎn)記住揖铜,reconciliation算法是一個(gè)實(shí)現(xiàn)細(xì)節(jié)。React可以在每個(gè)action上重新渲染整個(gè)應(yīng)用程序达皿;最終結(jié)果將是相同的天吓。我們經(jīng)常細(xì)化啟發(fā)式算法贿肩,以便使常見用例更快。
在當(dāng)前的實(shí)現(xiàn)中龄寞,你可以表達(dá)這個(gè)事實(shí)尸曼,一個(gè)子樹已經(jīng)被移動(dòng)到它的兄弟姐妹中,但你不知道它已經(jīng)被移動(dòng)到別的地方萄焦。該算法將重新渲染該完整子樹。
因?yàn)镽eact依賴于啟發(fā)式算法冤竹,如果不能滿足該算法設(shè)定的假設(shè)拂封,性能將受到影響。
- 該算法不會(huì)嘗試匹配不同組件類型的子樹鹦蠕。如果你發(fā)現(xiàn)自己在兩個(gè)具有非常相似輸出的組件類型之間徘徊冒签,你應(yīng)該使它們類型相同。在實(shí)踐中钟病,我們沒有發(fā)現(xiàn)這是一個(gè)問題萧恕。
- key應(yīng)該是穩(wěn)定、可預(yù)測(cè)和唯一的肠阱。不穩(wěn)定的key(如由
Math.random()
產(chǎn)生的)將導(dǎo)致許多組件實(shí)例和DOM節(jié)點(diǎn)被不必要地重新創(chuàng)建票唆,這可能導(dǎo)致子組件性能降級(jí)和丟失狀態(tài)。