React diff機制(比較虛擬DOM的機制)

申明

本文翻譯此處,我只是搬運工。翻譯不準的地方請參考原文
原文作者:Christopher Chedeau (@vjeux)

vjeux.jpg

正文

React是Facebook創(chuàng)造的構建用戶界面的javascript框架。設計始終把性能放在心上。在這篇文章我將介紹diff機制以及React的render流程,這樣我們就能自己優(yōu)化app了

Diff機制

在開始我們的工作之前,我們先看看下面這個例子React是怎樣工作的

var MyComponent = React.createClass({ 
render: function() { 
if (this.props.first) 
{ return <div className="first"><span>A Span</span></div>; } 
else { return <div className="second"><p>A Paragraph</p></div>; } 
} });

是你描述了你的UI,我們應該知道渲染的結果不是實際的DOM節(jié)點。他們只是很小的Javascript對象。我們稱之為虛擬DOM。

React像這樣找到從先前渲染到下一步渲染的最小步驟。舉個例子:如果我們掛載<MyComponent first={true} />變?yōu)閽燧d<MyComponent first={false} />,最后再不掛載它,DOM結構將像這樣變化:

第一步

  • 創(chuàng)建節(jié)點<div className="first"><span>A Span</span></div>

第一步到第二部

  • 把屬性className="first"替換為className="second"
  • 把節(jié)點<span>A Span</span>替換為<p>A Paragraph</p>

最后
刪除節(jié)點:<div className="second"><p>A Paragraph</p></div>

一級一級對應

(英文為Level by Level,應該是比較虛擬Dom樹的時候是一級一級對應)
找尋兩個任意樹結構之間最小的變動是個 O(n^3)問題,如你所想這顯然不適用,React使用簡單強大的啟發(fā)式算法使其時間復雜度接近 O(n).
React只會嘗試一級一級去比價樹,這樣能顯著減少其復雜性而且這不是一個損失,因為在web里極少有組件被移到樹中不同的級別去,他們通常在子級中橫向移動。

級級比較

列表

假如我們有一個組件,里面有5個迭代,而我們要在下次在中間插入一個新組件。只有這些信息很難知道兩個列表是如何對應的。
默認情況下,React將第一個列表的第一個組件,對應第二個列表的第二個組件,你可以提供key屬性去幫助react知道該怎么對應。

有key與無key

組件

一個React app通常由很多組件組成一個大樹,主要使用div。diff算法將只會比較有相同類的組件。
例如:如果我們把<Header>替換為<ExampleBlock>,React將會移除header接著創(chuàng)建example block。我們不必浪費我們寶貴的時間比較兩個幾乎沒有相似性的組件。

不同class的組件直接替換

事件代理

把事件綁到dom節(jié)點很慢,消耗內(nèi)存。而React采用更好的技術稱之為“事件代理”。React走的更遠,采用W3C兼容的事件系統(tǒng)。這意味著ie8的事件操作bugs已經(jīng)成為過去。所有的事件在不同瀏覽器中是一致的。
讓我們來討論它是如何實施的。一個事件監(jiān)聽將會被綁在document的初始節(jié)點上。當事件觸發(fā)時,瀏覽器會給我們觸發(fā)事件的DOM節(jié)點。為了在DOM結構中傳播事件,React并不在虛擬DOM中迭代。
取而代之的是,因為每個React組件都有唯一的id用來編碼層次結構。我們可以通過簡單的操作獲得id的所有父本。通過在一個hash map中存儲事件監(jiān)聽,我們發(fā)現(xiàn)它比直接在虛擬DOM中綁定要好。這里有一個例子表明一個事件是如何在虛擬DOM中傳播的
// dispatchEvent('click', 'a.b.c', event) clickCaptureListeners['a'](event); clickCaptureListeners['a.b'](event); clickCaptureListeners['a.b.c'](event); clickBubbleListeners['a.b.c'](event); clickBubbleListeners['a.b'](event); clickBubbleListeners['a'](event);
(個人理解ickBubbleListeners即時事件的hash map,‘a(chǎn)’,‘a(chǎn).b.c’即是每個組件的id)
瀏覽器為每個事件和事件監(jiān)聽創(chuàng)建一個事件對象。這樣你可以得到事件對象的引用甚至可以改變它。然而這樣也意味著大量的內(nèi)存分配。React在啟動時會分配一個對象池,當要創(chuàng)建一個事件對象時,就從那個對象池中重復利用,這樣大大減少了垃圾回收操作。

渲染

Batching(沒有合適詞翻譯。。。批量?)

任何時候只要你在一個組件中調用了setState,React將把這個組件標記為dirty(臟),在事件循環(huán)結束后,React將找到所有臟的組件并重新渲染它們。
就是說,每一次事件循環(huán)Dom都會跟新一下。這個特性是建造高性能app的關鍵,而且通常用javascript很難實現(xiàn)。在React中,你默認就有了這個特性。

臟值

子樹渲染

setState調用時,組件會重新build其子虛擬DOM。如果你在根節(jié)點上調用setState,那么整個app都會重新渲染,所有的組件,即使它沒有改變也會調用它的render方法。這聽起來很低效,但實際中,它工作很好應為我們沒有操作實際的DOM。
首先,我們談論的是顯示用戶界面。應為屏幕空間是有限的,通常我們只會同時顯示幾百到上千個elements。Javascript有足夠快的業(yè)務邏輯管理整個界面。
另一個很重要的是,當你寫React代碼時,不要一出現(xiàn)變化就在根節(jié)點上調用setState方法。你應該在接收變化事件的組件或其上面的組件上調用setState,你應該極少的在上層中調。這意味著變化只會在用戶交互的地方。

子樹渲染
有選擇的子樹渲染

最后,你可以通過下面的方法選擇阻止子樹的渲染:
boolean shouldComponentUpdate(object nextProps, object nextState)

通過判斷先前組件和下一個組件的屬性/狀態(tài),你能夠告訴React這個組件是否需要重新渲染。當合適的處理這將會顯著提高性能。
為了使用它,你必須能比較對象的差異,這里就牽扯到一些問題,如是否應該深度比較,如果深度比較,我么是否應該固定數(shù)據(jù)的結構,或是做深度拷貝。
而且你應該記住,這個函數(shù)總會被調用,所以你要確保自己寫的函數(shù)的調用時間要比默認的啟發(fā)式比較的時間少。

選擇性渲染

結論

使React快的技術不是新的。我們很早就知道直接操作DOM很費時,你必須將寫操作,讀操作,和事件代理批量處理,這樣更快。。。
人們依舊在談論它們,因為在實際中實施很困難。是什么是React脫穎而出,就是因為這些優(yōu)化是默認實施的,這使你不必搬起石頭砸自己的腳,不會使app運行的很慢。
React性能成本模型也很好理解:每次setState都會重新渲染子樹。如果你想提高性能,就盡量在低層次結構中調用setState或者使用shouldComponentUpdate去阻止渲染很大的子樹。

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

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

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