關於react diff 算法(譯文)

React是由facebook開發,用於構建用戶界面的js類庫,以提高性能爲設計理念。在本文中,我將爲你們介紹在React中的diff算法,以及它的渲染機制,以便於你可以更好的優化你的程序。react

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。渲染結果只是輕量級的JavaScript對象。咱們稱之爲虛擬DOM。
React將使用此表示法來嘗試找到從上一個渲染到下一個渲染的最小步數,好比,若是咱們要用 <MyComponent first={false} />去替代<MyComponent first={true} />,插入真實DOM, 而後移除它,下面是DOM指令的結果:瀏覽器

從無到有
建立DOM節點:<div className="first"><span>A Span</span></div>bash

從一到二
className="second"去替換屬性 className="first"
<p>A Paragraph</p> 去替換節點<span>A Span</span>數據結構

從二到無
移除節點:<div className="second"><p>A Paragraph</p></div>函數

逐級比較

尋找兩個任意樹之間最小的修改數須要執行n3次,你能夠想象,這對於咱們來講是難以接受的。而React用了一個簡單並且目前來講都很是強大的計算方法去找到他們的變化,而執行的次數僅僅爲n次。性能

React經過逐級的去比較兩顆節點樹的差別,這大大下降了複雜性,並且精準度上損失也不大,由於在Web應用程序中將組件樹不一樣級的移動比較是很是罕見的。 組件一般只能在子組件橫向移動。優化

列表List

假設咱們有一個組件,它在一個迭代中渲染了5個組件,而下一次渲染的時候在組件列表的中間插入一個新的組件。 只是經過這個信息真的很難知道如何在兩個組件列表之間進行映射。ui

默認狀況下,React將先前列表的第一個組件與下一個列表的第一個組件相關聯,等等。您能夠提供一個Key屬性,以幫助React去找到他們的映射關係。 在實際中,這一般很容易把剛剛插入的組件從他們當中找出來。
this

組件

一個React應用程序一般由許多用戶定義的組件組成,最終會變成一個主要由div組成的樹。 經過diff算法考慮了這些附加信息,由於React只匹配具備相同類的組件。

例如,若是<Header><ExampleBlock>替換,則React將刪除<Header>並建立一個<ExampleBlock>。 咱們不須要花費寶貴的時間嘗試匹配不太可能有類似之處的兩個組件。

事件委託

將事件監聽器附加到DOM節點是痛苦的緩慢並且消耗內存的事情。可是,React實現了一種稱爲「事件委託」的流行技術。 React更進一步,並從新實現了符合W3C標準的事件系統。這意味着Internet Explorer 8事件處理兼容問題將成爲過去的事情,全部的事件名稱在瀏覽器之間是一致的。

讓我解釋一下它的實現。單個事件監聽器附加到文檔的根節點上。當事件被觸發時,瀏覽器告訴咱們觸發事件的DOM節點。爲了經過DOM節點傳播事件,React並無在虛擬DOM層次結構上進行迭代。

相反,每一個React組件都有惟一id用來表示他的層級。 咱們可使用簡單的字符串操做來獲取全部父節點的id。 經過將事件偵聽器存儲在哈希映射中,咱們發現它比將它們附加到虛擬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);複製代碼

瀏覽器爲每一個事件和監聽器建立一個新的事件對象。 這個對象有一個很是好的的屬性,你能夠保留事件對象的引用甚至修改它。 可是,這意味着要作大量的內存分配。React在一開始會爲這些對象分配內存池。 每當須要事件對象時,它將從內存池中被從新使用,這大大減小了內存垃圾回收。

渲染

批量處理:

每當您在組件上調用setState時,React會將其標記爲髒。 在事件循環結束時,React會查看全部髒組件並從新渲染它們。若是是批處理也就意味着在事件循環期間,正在更新渲染DOM的時間正好是一次。 這個屬性是構建一個應用程序的性能好壞的關鍵,可是寫經常使用的JavaScript很是難以得到。 在React應用程序中,默認狀況下能夠得到。

子樹渲染:

調用setState時,組件將重建其子項的虛擬DOM。若是您在根元素上調用setState,則會從新渲染整個React應用程序。全部的組件,即便它們沒有改變,也會使用它們的渲染方法。這可能聽起來很可怕,效率低下,但在實踐中,這樣能夠正常工做,由於咱們沒有碰到實際的DOM。
首先,咱們討論一下顯示用戶界面。因爲屏幕空間有限,您一般一次按順序的顯示數百到數千個元素。 JavaScript已經能夠足夠快的速度處理業務邏輯和整個接口管理。
另外一個重要的一點,當編寫React代碼時,每次發生變化一般不會都在根節點上調用setState。你能夠在接收到事件變化的組件或父組件上調用它。咱們不多走到頂端,一般用戶的交互都是發生在對應的組件上變化。

選擇子樹渲染:

最後,您能夠防止一些子樹從新渲染。 若是在組件上使用如下方法:
boolean shouldComponentUpdate(object nextProps, object nextState)
基於組件的上一個和下一個props/state,您能夠告訴React該組件沒有更改,而且不須要從新渲染。 正確使用該方法能夠大大提升性能。爲了可以使用它,您須要比較JavaScript對象。這裏面仍是有不少問題的,好比js對象的比較是深點仍是淺點,若是深度比較,我是用不可變數據結構仍是作深拷貝。並且你還要記住,這個函數將一直被調用,因此你想要確保計算時間要比渲染組件所用的時間要少,即便渲染是多餘的。

到底什麼狀況下使用shouldComponentUpdate?
    按照React團隊的說法,shouldComponentUpdate是保證性能的緊急出口  
    http://jamesknelson.com/should-i-use-shouldcomponentupdate
    http://www.infoq.com/cn/news/2016/07/react-shouldComponentUpdate複製代碼

結論

讓React變得如此快的技術已經不是什麼新鮮的事情,並且咱們好久以前就知道,操做DOM是昂貴的,因此你應該對DOM進行批量的讀寫、使用事件委託,這些都能使你的程序變得更快。
你們還一直在討論React, 由於事實上,使用常規的Javascript代碼很難去實現這些優化的方法,而React默認就能實現,這也是爲何React爲何能脫穎而出的緣由。
React的性能成本模型也很容易理解:每次setState都會從新呈現整個子樹。 若是要提升性能,請儘量少調用setState,並使用shouldComponentUpdate來防止從新全部子樹。

原文地址:calendar.perfplanet.com/2013/diff/

相關文章
相關標籤/搜索