React提供了一個聲明式地API所以你不用擔憂每一次更新什麼東西改變了。這使得開發應用變得簡單,可是這個東西在React中如何實現的並非很明顯。這篇文章會解釋咱們在React的算法中所作的選擇以便組件的更新是可預測的當爲了高性能的app而變得速度足夠快。算法
目的數組
當你使用React,在一個單獨的時間點你能夠考慮render()函數會建立一棵React元素的樹。當下一次state或者props更新了,render()函數將會返回一個不一樣的樹。React隨後須要解決怎樣有效率地更新UI去匹配最新的樹。app
對於這個最小化轉變一個樹到另外一個樹的操做的算法問題,這裏有一些通用的解決辦法。然而,樹中元素個數爲n,最早進的算法 的時間複雜度爲O(n^3) 。dom
若是咱們在React裏使用這個算法,顯示1000個元素將會要求按次序發生十億次比較。這樣的比較的花費過高昂了。React實現了一個探索式的O(n)算法基於兩種假設:函數
實際上,這些假設幾乎對全部實際使用場景都是有效的。性能
對比算法spa
當比較兩棵樹的時候,React首先比較兩個根元素。根據根元素的type不一樣比較的行爲也不一樣。code
不一樣類型的元素component
不管什麼時候根元素若是有不一樣的類型,React會銷燬舊的樹而後從草稿中建立新的樹。從<a>變成<img>,或者從<Article>變成<Comment>,或者從<Button>變成<div>,任何這樣的改變都會使得整個樹重建。blog
當銷燬一個樹的時候,舊的DOM節點就被銷燬了。組件實例調用componentWillUnmount()方法。當建立了一個新的樹,新的DOM節點被插入已有的DOM。組件實例依次調用componentWillMount()方法和以後的componentDidMount()方法。任何舊的樹的state都會丟失。
任何根元素之下的組件也會被銷燬而且state也丟失。舉個例子,當比較不一樣的時候:
<div> <Counter /> </div> <span> <Counter /> </span>
這樣會銷燬舊的Counter而後從新創建一個新的。
相同類型的DOM元素
當比較兩個相同類型的React DOM元素的時候,React會觀察它們的屬性,保留相同的DOM節點,只更新改變了的屬性。舉個例子:
<div className="before" title="stuff" /> <div className="after" title="stuff" />
經過比較這兩個元素,React知道只去修改className屬性。
當更新style的時候,React也知道只去更新改變了的屬性。舉個例子:
<div style={{color: 'red', fontWeight: 'bold'}} /> <div style={{color: 'green', fontWeight: 'bold'}} />
當改變發生在這兩個元素之間,React知道只去修改color樣式,而不修改fontWeight。
在處理DOM節點以後,React以後會在子元素上遞歸計算。
相同類型的組件元素
當一個組件更新的時候,實例保持同樣,以便渲染的時候state被維持。React更新底層組件實例的props去產生新元素,而且在底層實例上調用componentWillReceiveProps()和componentWillUpdate()。
接下來,render()方法被調用而且diff算法在以前的結果和新結果上遞歸處理。
子節點的遞歸
默認狀況,當在DOM節點的子節點上遞歸時,React僅在同一時間點遞歸兩個子節點列表,並在有不一樣時產生一個變動。
舉個例子,當在子元素的結尾添加一個元素的時候,兩個樹之間的轉變:
<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>樹。
若是你很簡單地實現它,在起始位置插入一個元素那樣性能就會不好。舉個例子,下面兩個樹的轉變就很差:
<ul> <li>Duke</li> <li>Villanova</li> </ul> <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul>
React會使每個子元素變化而不是保持<li>Duke</li>和<li>Villanova</li>子樹不變。這樣的低效率會成爲一個問題。
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>
如今React知道了擁有2014key的元素是新元素,而擁有2015和2016的元素只是須要移動一下位置。
事實上,找到一個key一般不麻煩。你將要去顯示的元素也許已經有了一個惟一的ID,所以key屬性也可使用你的數據:
<li key={item.id}>{item.name}</li>
若是不是這種狀況,你能夠添加新的ID屬性到你的模型或者哈希一些內容來生成一個key。這個key對於兄弟元素必須惟一,可是不須要全局惟一。
萬不得已,你能夠傳遞它們在數組裏的索引做爲一個key。若是數組不會從新排序那麼這樣也會起做用,可是若是從新排序程序也會變慢。
當索引用做key時,組件狀態在從新排序時也會有問題。組件實例基於key進行更新和重用。若是key是索引,則item的順序變化會改變key值。這將致使受控組件的狀態可能會以意想不到的方式混淆和更新。
這裏是在CodePen上使用索引做爲鍵可能致使的問題的一個例子,這裏是同一個例子的更新版本,展現瞭如何不使用索引做爲鍵將解決這些reordering, sorting, 和 prepending的問題。
權衡
牢記協調算法的實現細節很是重要。React可能在每一次動做的時候會渲染整個app;最終的結果會是同樣的。咱們要按期地提煉這些啓發式算法爲了讓相同的使用場景性能更好。
在現在的實現裏,你能夠表示這個事實,一個子樹在它的兄弟節點之中移動,可是你不能告知它移動到哪裏。算法將會渲染整個子樹。
由於React依賴於啓發式算法,若是在它們背後的設想沒有被知足,那麼性能會受損。