React文檔(二十一)協調

React提供了一個聲明式地API所以你不用擔憂每一次更新什麼東西改變了。這使得開發應用變得簡單,可是這個東西在React中如何實現的並非很明顯。這篇文章會解釋咱們在React的算法中所作的選擇以便組件的更新是可預測的當爲了高性能的app而變得速度足夠快。算法

目的數組

當你使用React,在一個單獨的時間點你能夠考慮render()函數會建立一棵React元素的樹。當下一次state或者props更新了,render()函數將會返回一個不一樣的樹。React隨後須要解決怎樣有效率地更新UI去匹配最新的樹。app

對於這個最小化轉變一個樹到另外一個樹的操做的算法問題,這裏有一些通用的解決辦法。然而,樹中元素個數爲n,最早進的算法 的時間複雜度爲O(n^3) 。dom

若是咱們在React裏使用這個算法,顯示1000個元素將會要求按次序發生十億次比較。這樣的比較的花費過高昂了。React實現了一個探索式的O(n)算法基於兩種假設:函數

  1. 兩個元素類型不一樣會形成不一樣的樹
  2. 藉助一個key屬性,開發者能夠暗示哪個子元素在不一樣的渲染的時候會穩定不變

實際上,這些假設幾乎對全部實際使用場景都是有效的。性能

對比算法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屬性

爲了解決這個問題,React支持一個key屬性。當一個子元素擁有key屬性,React使用key來匹配原始的樹和以後的新樹。舉個例子,添加一個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依賴於啓發式算法,若是在它們背後的設想沒有被知足,那麼性能會受損。

  1. 這個算法不會去試圖匹配組件類型不一樣的子樹。若是你看到交替的兩種組件類型有着類似的輸出,你也許想要使它們的類型同樣。事實上,咱們尚未發現這樣會出現問題。
  2. key屬性必須穩定,可預測還有惟一。不穩定的key(就像那些經過Math.random()生成的隨機數)將會形成許多組件實例和DOM節點沒必要要的重複建立,這樣就會使性能變差而且在子組件中丟失state。
相關文章
相關標籤/搜索