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