React進(jìn)階筆記8(協(xié)調(diào)Reconciliation)

協(xié)調(diào)(Reconciliation)

React提供了一組聲明式的API,讓你不必關(guān)心每次更新的變化,這樣使得應(yīng)用的編寫容易了很多。
但是在React中如何實(shí)現(xiàn)還并不太清晰,這篇文章解釋了React對(duì)比算法的選擇,讓組件更新可預(yù)測(cè)并且使得高性能足夠的快。

目的

當(dāng)你使用React,在單一的時(shí)間點(diǎn),你可以考慮render()函數(shù)作為創(chuàng)建React函數(shù)的樹,React需要算出如何更新UI來匹配最新的樹(dom)

有一個(gè)解決方案是:

將一棵樹轉(zhuǎn)換為另一棵樹的最小操作數(shù)算法問題的通用方案。然而樹種元素的個(gè)數(shù)為n,最先進(jìn)的算法 的時(shí)間復(fù)雜度為O(n3) 。

如果我們?cè)赗eact中使用,展示1000個(gè)元素,則需要10億次的比較,這樣的操作臺(tái)昂貴。相反,React基于這兩點(diǎn)的假設(shè),實(shí)現(xiàn)了一個(gè)啟發(fā)的O(n)算法:

①兩個(gè)不同類型的元素將產(chǎn)生兩顆不同的樹
②通過渲染器附帶的key屬性,開發(fā)者可以示意,那些子元素是穩(wěn)定的。

實(shí)踐中,這種假設(shè)適用于大部分的應(yīng)用場(chǎng)景的。

對(duì)比算法

當(dāng)對(duì)比兩棵樹時(shí),React首先比較他們的根節(jié)點(diǎn)。根節(jié)點(diǎn)的type不同,他們的行為也不同。

不同類型的元素

每當(dāng)根元素有不同的類型,React就會(huì)卸載舊樹,創(chuàng)建新樹,從<a><img>或從<Article><Comment>,或從<Button><div>,任何的調(diào)整都會(huì)導(dǎo)致全部重建。

當(dāng)樹被卸載,舊的DOM節(jié)點(diǎn)將被銷毀。組件實(shí)例會(huì)調(diào)用componentWillUnmount()。當(dāng)構(gòu)建一棵新樹,新的DOM節(jié)點(diǎn)被插入到DOM中。組件實(shí)例將依次調(diào)用componentWillMount()componentDidMount()。任何與舊樹有關(guān)的狀態(tài)都將丟棄。

這個(gè)根節(jié)點(diǎn)下,所有的組件都會(huì)被卸載,同時(shí)他們的狀態(tài)會(huì)被銷毀。
以下的節(jié)點(diǎn)對(duì)比前后:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

由于根節(jié)點(diǎn)換了,所以組件<Counter>將會(huì)重載新的組件。

相同類型的DOM元素

當(dāng)比較2個(gè)相同的React DOM元素時(shí),React則會(huì)觀察兩者的屬性。

當(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元素的className

當(dāng)更新style時(shí),React同樣知道僅更新變更的屬性。例如:

<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />

當(dāng)在調(diào)整兩個(gè)元素時(shí),React知道僅改變color樣式而不是fontWeight。

在處理完DOM元素后,React遞歸其子元素。

相同類型的組件元素

當(dāng)組建更新時(shí),實(shí)例還是保持一致。這樣能讓狀態(tài)在渲染之間保留。React通過更新底層組件的props來渲染新的元素,并且在底層的組件上,依次調(diào)用componentWillRevicePropscomponentWillUpdate的方法。

接下來render()方法被調(diào)用,同時(shí)對(duì)比算法 遞歸處理之前的結(jié)果和新的結(jié)果。

遞歸子節(jié)點(diǎn)

默認(rèn)情況下,當(dāng)遞歸DOM節(jié)點(diǎn)的子節(jié)點(diǎn),React只在同一個(gè)時(shí)間點(diǎn),遞歸2個(gè)子節(jié)點(diǎn)列表。并且在有發(fā)生不同的時(shí)候,產(chǎn)生一個(gè)變更。

如,當(dāng)在子節(jié)點(diǎn)末尾增加一個(gè)元素,兩棵樹的轉(zhuǎn)換效果很好:

<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React將會(huì)匹配兩棵樹的<li>first</li>,并匹配兩棵樹的<li>second</li>節(jié)點(diǎn),并插入<li>third</li>節(jié)點(diǎn)樹。

如果使用原生實(shí)現(xiàn),在開始插入元素,會(huì)使得性能更加棘手,例如,兩棵樹的轉(zhuǎn)換效果則比較糟糕:

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React會(huì)調(diào)整每一個(gè)子節(jié)點(diǎn),而非意識(shí)到可以完整保留<li>Duke</li><li>Villanova</li>子樹。低效成了一個(gè)問題。

keys

為了解決以上這個(gè)低效的問題,React支持了一個(gè)key屬性,當(dāng)子節(jié)點(diǎn)有key時(shí),React會(huì)用key來匹配原本樹的子節(jié)點(diǎn)和新樹的子節(jié)點(diǎn),增加key可以讓之前效率不高的樣例中使變得高效。

<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知道帶有'2014'的key的元素是新的,并僅移動(dòng)帶有'2015'和'2016'的key的元素。

實(shí)踐中,發(fā)現(xiàn)key通常不難。你將展示的元素可能已經(jīng)帶有一個(gè)唯一的ID,因此key可以來自于你的數(shù)據(jù)中:

<li key={item.id}>{item.name}</li>

當(dāng)這已不再是問題,你可以給你的數(shù)據(jù)增加一個(gè)新的ID屬性,或根據(jù)數(shù)據(jù)的某些內(nèi)容創(chuàng)建一個(gè)哈希值來作為key。

key必須在其兄弟節(jié)點(diǎn)中是唯一的,而非全局唯一。

萬不得已,你可以傳遞他們?cè)跀?shù)組中的索引作為key。若元素沒有重排,該方法效果不錯(cuò),但重排會(huì)使得其變慢。

索引用作key時(shí),組件狀態(tài)在重新排序時(shí)也會(huì)有問題。組件實(shí)例基于key進(jìn)行更新和重用。如果key是索引,則item的順序變化會(huì)改變key值。這將導(dǎo)致受控組件的狀態(tài)可能會(huì)以意想不到的方式混淆和更新。

這里是在CodePen上使用索引作為鍵可能導(dǎo)致的問題的一個(gè)例子,這里是同一個(gè)例子的更新版本,展示了如何不使用索引作為鍵將解決這些reordering, sorting, 和 prepending的問題。

權(quán)衡

牢記協(xié)調(diào)算法的實(shí)現(xiàn)細(xì)節(jié)非常重要。React可能會(huì)在每次操作時(shí)渲染整個(gè)應(yīng)用;而結(jié)果仍是相同的。為保證大多數(shù)場(chǎng)景效率能更快,我們通常提煉啟發(fā)式的算法。

在目前實(shí)現(xiàn)中,可以表明一個(gè)事實(shí),即子樹在其兄弟節(jié)點(diǎn)中移動(dòng),但你無法告知其移動(dòng)到哪。該算法會(huì)重渲整個(gè)子樹。

由于React依賴于該啟發(fā)式算法,若其背后的假設(shè)沒得到滿足,則其性能將會(huì)受到影響:

1.算法無法嘗試匹配不同組件類型的子元素。若你發(fā)現(xiàn)兩個(gè)輸出非常相似的組件類型交替出現(xiàn),你可能希望使其成為相同類型。實(shí)踐中,我們并非發(fā)現(xiàn)這是一個(gè)問題。

2.Keys應(yīng)該是穩(wěn)定的,可預(yù)測(cè)的,且唯一的。不穩(wěn)定的key(類似由Math.random()生成的)將使得大量組件實(shí)例和DOM節(jié)點(diǎn)進(jìn)行不必要的重建,使得性能下降并丟失子組件的狀態(tài)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容