?
This document uses PHP Chinese website manual Release
React提供了一個聲明式API,因此您無需擔心每次更新的確切更改。這使得編寫應(yīng)用程序變得更加簡單,但在React中如何實現(xiàn)它可能并不明顯。本文解釋了我們在React的“差異化”算法中所做的選擇,以便組件更新可預測,同時對于高性能應(yīng)用程序足夠快。
當您使用React時,您可以在單個時間點將該render()
功能視為創(chuàng)建React元素樹。在下一個狀態(tài)或道具更新時,該render()
函數(shù)將返回不同的React元素樹。然后React需要弄清楚如何有效地更新UI以匹配最近的樹。
對于產(chǎn)生將一棵樹轉(zhuǎn)換為另一棵樹的最小操作次數(shù)的算法問題,存在一些通用解決方案。然而,現(xiàn)有技術(shù)的算法在O(n3)的順序中具有復雜性,其中n是樹中元素的數(shù)量。
如果我們在React中使用它,則顯示1000個元素需要10億次比較。這太貴了。相反,React基于兩個假設(shè)實現(xiàn)了啟發(fā)式O(n)算法:
兩種不同類型的元素會產(chǎn)生不同的樹木。
2. 開發(fā)人員可以通過key
道具暗示哪些子元素可以在不同的渲染器中保持穩(wěn)定。
實際上,這些假設(shè)對于幾乎所有的實際使用情況都是有效的。
在分析兩棵樹時,React首先比較兩個根元素。行為根據(jù)根元素的類型而不同。
每當根元素具有不同類型時,React就會拆除舊樹并從頭開始構(gòu)建新樹。從去<a>
到<img>
,或者<Article>
到<Comment>
,或<Button>
到<div>
-任何這些將導致完全重建。
拆除樹時,舊的DOM節(jié)點被破壞。組件實例接收componentWillUnmount()
。在構(gòu)建新樹時,將新的DOM節(jié)點插入到DOM中。組件實例接收componentWillMount()
然后componentDidMount()
。任何與舊樹相關(guān)的狀態(tài)都會丟失。
根以下的任何組件也將被卸載并且其狀態(tài)被破壞。例如,差異時:
<div> <Counter /></div><span> <Counter /></span>
這將摧毀舊的Counter
并重新裝上一個新的。
比較相同類型的兩個React DOM元素時,React查看兩者的屬性,保持相同的底層DOM節(jié)點,并僅更新已更改的屬性。例如:
<div className="before" title="stuff" /><div className="after" title="stuff" />
通過比較這兩個元素,React知道只修改className
底層DOM節(jié)點。
更新時style
,React也知道只更新已更改的屬性。例如:
<div style={{color: 'red', fontWeight: 'bold'}} /><div style={{color: 'green', fontWeight: 'bold'}} />
在這兩個元素之間轉(zhuǎn)換時,React知道只修改color
樣式,而不是fontWeight
。
在處理DOM節(jié)點之后,React會在子節(jié)點上遞歸。
當組件更新時,實例保持不變,以便在整個渲染過程中維護狀態(tài)。反應(yīng)更新底層組件實例的道具新元素匹配,并呼吁componentWillReceiveProps()
和componentWillUpdate()
對底層的實例。
接下來,該render()
方法被調(diào)用,并且diff算法對先前結(jié)果和新結(jié)果進行遞歸。
默認情況下,React在DOM節(jié)點的子節(jié)點上遞歸時,只是同時迭代兩個子節(jié)點列表,并在出現(xiàn)差異時生成突變。
例如,在兒童的末尾添加元素時,在這兩棵樹之間轉(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>
樹。
如果你天真地實現(xiàn)它,在開始插入一個元素會導致性能下降。例如,在這兩棵樹之間轉(zhuǎn)換效果不佳:
<ul> <li>Duke</li> <li>Villanova</li></ul><ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li></ul>
反應(yīng)會產(chǎn)生變異每一個孩子,而不是意識到它可以保留的<li>Duke</li>
和<li>Villanova</li>
子樹完好無損。這種低效率可能是一個問題。
為了解決這個問題,React支持一個key
屬性。當孩子擁有密鑰時,React使用該密鑰將原始樹中的孩子與后續(xù)樹中的孩子進行匹配。例如,在key
上面的低效率示例中添加a 可以使樹轉(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)在陣營都知道,與關(guān)鍵元素'2014'
是新的,并用鑰匙元素'2015'
和'2016'
剛剛搬進了。
在實踐中,找到一個關(guān)鍵通常并不難。您要顯示的元素可能已經(jīng)有一個唯一的ID,所以密鑰可以來自您的數(shù)據(jù):
<li key={item.id}>{item.name}</li>
如果情況并非如此,您可以將新的ID屬性添加到您的模型中,或者對內(nèi)容的某些部分進行散列以生成密鑰。關(guān)鍵只在兄弟姐妹中是唯一的,而不是全球唯一的。
作為最后的手段,您可以將項目的索引作為關(guān)鍵字傳遞給數(shù)組。如果項目從未重新排序,這可以很好地工作,但重新排序會很慢。
請務(wù)必記住,協(xié)調(diào)算法是一個實現(xiàn)細節(jié)。React可以在每一個動作上重新渲染整個應(yīng)用程序; 最終結(jié)果將是相同的。為了清楚起見,在這種情況下重申意味著要求render
所有組件,但這并不意味著React將卸載并重新安裝它們。它只會按照前面章節(jié)中所述的規(guī)則來應(yīng)用差異。
我們經(jīng)常改進啟發(fā)式,以便更快地制作常見用例。在目前的實現(xiàn)中,你可以表達一個事實,即一棵子樹已經(jīng)在它的兄弟姐妹之間移動了,但是你不能說它已經(jīng)移動到別的地方了。該算法將重新渲染完整的子樹。
因為React依賴于啟發(fā)式算法,如果它們背后的假設(shè)不符合,性能將受到影響。
該算法不會嘗試匹配不同組件類型的子樹。如果您發(fā)現(xiàn)自己在輸出類似的兩種組件類型之間交替,您可能希望使它成為相同的類型。實際上,我們并未發(fā)現(xiàn)這是一個問題。
2. 密鑰應(yīng)該是穩(wěn)定的,可預測的和獨特的。不穩(wěn)定的鍵(如由那些產(chǎn)生的鍵Math.random()
)將導致許多組件實例和DOM節(jié)點被不必要地重新創(chuàng)建,這可能導致性能下降并丟失子組件中的狀態(tài)。