React提供了一個聲明式的API,因此你沒必要擔憂每次DOM更新時內部會修改哪些東西。雖然在React中並非那麼明顯地告訴你具體如何實現的,不過這也讓編寫應用變得更加容易。javascript
本文會詳細解釋在React中的「diffing」
算法是怎麼作的,以便組件更新是可預測的,從而讓高性能應用變得足夠快。java
當使用React時,在單個時間點,您能夠將render()
函數看作是在建立React元素樹。 在下一個state
或props
更新時render()
函數將返回一個不一樣的React元素樹。 React須要弄清楚如何高效地更新UI去匹配上最新的元素樹。算法
對於將一個樹變換成另外一個樹的最小操做數的算法問題,如今已經存在一些比較通用的解決方案。 然而,那些現有的最早進的技術算法都有O(n^3)
的複雜度(n是樹中的元素的數量)。數組
若是在React中使用這些算法,顯示1000個元素將須要大約十億次比較。 這個真的代價太昂貴了。 相反,React實現了一個基於兩個假設
直觀推斷出的O(n)
算法:dom
不一樣類型的兩個元素將產生不一樣的樹。函數
開發人員能夠在不一樣渲染之間使用key
屬性來表示哪些子元素是穩定的。性能
實際上,這兩條假設對幾乎全部的實際使用都是有效的。spa
當比較兩棵DOM樹的差別時,React首先比較兩個根元素。 若是根元素的類型不一樣,那麼行爲也是不一樣的。code
每當根元素是不一樣的類型時,React將刪除舊的DOM樹並從頭開始從新構建新的DOM樹。 從<a>
到<img>
、從<Article>
到<Comment>
、從<Button>
到<div>
,只要不同就會徹底從新構建。component
當刪除就的DOM樹時,舊的DOM節點也被刪掉。 這個時候組件實例觸發componentWillUnmount()
函數 。當構建一個新的DOM樹時,新的DOM節點會被插入到DOM中。 組件實例觸發componentWillMoun()
和componentDidMount()
。 與以前舊的DOM樹相關聯的任何state
也都將丟失。
在根元素之下的任何組件將被卸載而且它們的state
也會所有丟失。 例如:
// 從 <div> <Counter /> </div> // 變爲 <span> <Counter /> </span>
由於根元素從div
變爲了span
,因此舊的Counter
組件將被銷燬,而後再從新構建一個新的。
當比較相同類型的兩個React DOM元素時,React會先查看二者的屬性差別,而後保留相同的底層DOM節點,僅僅去更新那些被更改的屬性。 例如:
<div className="before" title="hello" /> <div className="after" title="hello" />
經過比較這兩個元素屬性,React就會知道只須要修改底層DOM節點上的className
便可。
當更新style
屬性時,React也會知道只須要更新style
中的那些已更改的屬性。 例如:
<div style={{color: 'red', width: '300px'}} /> <div style={{color: 'red', width: '400px'}} />
當在這兩個元素之間轉換時,React知道只需修改width
,而不是color
。
處理根DOM節點後,React會根據上面的判斷邏輯對子節點進行遞歸掃描。
當組件更新時,實例保持不變,所以在不一樣的渲染之間組件內的state
是保持不變的。 React會更新底層組件實例的props
來匹配新元素,並在底層實例上調用componentWillReceiveProps()
和componentWillUpdate()
。
接下來,調用render()
方法,diff
算法就會對上一個結果和新結果進行遞歸比較。
默認狀況下,當對DOM節點的子元素進行遞歸時,React只是同時迭代兩個子元素lists,並在有差別時產生變化。
例如,當在子元素的末尾再添加一個元素時,這兩個樹之間就會有一個的很好轉換效果:
<ul> <l1>one</li> <li>two</li> </ul> <ul> <li>one</li> <li>two</li> <li>three</li> </ul>
React將匹配兩個<li>one</li>
樹,匹配兩個<li>two</li>
樹,而後插入一個<li>three</li>
樹。
可是,不要太天真了。若是在子元素的開頭部分插入一個元素的話,性能會便的不好。 例如,這兩棵樹之間的轉換效果就不好:
<ul> <li>one</li> <li>two</li> <ul> <ul> <li>zero</li> <li>one</li> <li>two</li> <ul>
這種狀況React將更改每一個子元素 ,而不會意識到它能夠保持<li>one</li>
和<li>two</li>
子元素樹無缺無損。 這種低效率的狀況是一個必須注意的問題。
爲了解決上面的問題,React提供了一個key
屬性。 當子元素有key
屬性時,React使用key
將原始樹中的子元素與後續樹中的子元素進行匹配。 例如,上面的那個低效例子添加一個key
就可讓子元素樹轉換變的頗有效:
<ul> <li key="1">one</li> <li key="2">two</li> <ul> <ul> <li key="0">zero</li> <li key="1">one</li> <li key="2">two</li> <ul>
如今React就能夠知道key="0"
的元素是新的,而且key="1"
和key="2"
的元素只需移動便可。
在實踐中,使用一個惟一的key
並不難。 您要顯示的元素可能已具備惟一的ID
,所以key
能夠來自你本身的數據中:
<li key={item.id>{item.name}</li>
若是不是這樣,你能夠向數據模型中給每一項數據添加一個新的ID
屬性,或者對內容的某些部分進行哈希生成key
。 key
屬性只有在其兄弟元素之間是惟一的,並非全局惟一的。
最後一種方式是能夠將數組中的索引做爲key
。 若是數組中的每一項不須要從新排序,一樣也能夠很好地工做,可是萬一須要從新排序的話,這會變的很慢。
要記住重要的是,diffing
算法是一個具體的實現細節。 React能夠在每一個操做上去從新渲染應用; 最終結果都是同樣的。
在當前的實現中,你能夠看到一個事實是一個子樹已經成功移動到它的兄弟元素當中,但你不能告訴它已經移動到別的地方。 該算法將從新渲染這個完整的子元素樹。
由於React很依賴這個直觀推斷的算法來判斷DOM是否須要從新處理,若是不能知足這個算法的那兩個假設條件前提,應用的性能將會受到很大影響。
該算法不會去嘗試匹配那些不一樣組件類型的子元素樹。 若是你看到本身在返回類似輸出結果的兩個組件類型之間來來回回,你可能須要把它們改成相同的類型組件。
key
屬性應該是穩定,可預測和惟一的。 不穩定的鍵(如使用Math.random()
生的key
)將致使許多組件實例和DOM節點進行沒必要要地重複建立,這可能致使子組件中的性能下降和state
丟失。