查找 React 中文文檔請往 https://doc.react-china.org/
原文: http://calendar.perfplanet.co...
做者: Christopher Chedeau
中文社區導航: http://nav.react-china.orgjava
React 是 Facebook 開發的構建用戶界面的類庫.
它從設計之初就將性能做爲重點.
這篇文章我展現 diff 算法和渲染過程怎樣工做, 你能夠借鑑優化本身的應用.react
在深刻實現的細節以前, 須要瞭解一下 React 怎樣工做的.git
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 看起來是什麼樣子.
須要特別注意, render 執行的結果獲得的不是真正的 DOM 節點.
結果僅僅是輕量級的 JavaScript 對象, 咱們稱之爲 virtual DOM.github
React 要從這個表現形式當中嘗試找到前一個渲染結果到後一個的最小步數.
好比, 當咱們掛載了 <MyComponent first={true} />
, 而後用 <MyComponent first={false} />
替換, 而後又取消掛載,
這樣一個過程的 DOM 的指令是這樣的:算法
從沒有到第一步瀏覽器
<div className="first"><span>A Span</span></div>
第一步到第二步數據結構
className="first" 到 className="second"
<span>A Span</span>
到 <p>A Paragraph</p>
第二步到沒有app
<div className="second"><p>A Paragraph</p></div>
找到兩棵任意的樹之間最小的修改是一個複雜度爲 O(n^3)
的問題.
你能夠想象, 咱們的例子裏這不是容易處理的.
React 用了一種簡單可是強大的技巧, 達到了接近 O(n)
的複雜度.函數
React 僅僅是嘗試把樹按照層級分解. 這完全簡化了複雜度,
並且也不會失去不少, 由於 Web 應用不多有 component 移動到樹的另外一個層級去.
它們大部分只是在相鄰的子節點之間移動.性能
假設咱們有個 component, 一個循環渲染了 5 個 component,
隨後又在列表中間插入一個新的 component.
只知道這些信息, 要弄清兩個 component 的列表怎麼對應很難.
默認狀況下, React 會將前一個列表第一個 component 和後一個第一個關聯起來, 後面也是.
你能夠寫一個 key
屬性幫助 React 來處理它們之間的對應關係.
實際中, 在子元素中找到惟一的 key 一般很容易.
React app 一般由用戶定義的 component 組合而成,
一般結果是一個主要是不少 div
組成的樹.
這個信息也被 React 的 diff 算法考慮進去, React 只會匹配相同 class 的 component.
好比, 若是有個 <Header>
被 <ExampleBlock>
替換掉了,
React 會刪除掉 header 再建立一個 example block.
咱們不須要花寶貴的時間去匹配兩個不大可能有類似之處的 component.
往 DOM 節點上綁事件慢得人心碎啊, 並且很費內存.
做爲替代, React 部署了種流行技術, 叫作"事件代理".
React 走得更遠, 從新實現了一遍符合 W3C 規範的事件系統.
意味着 IE8 事件處理的 bug 成爲過去了, 全部事件名稱跨瀏覽器保持一致.
這裏解釋一下怎麼實現的. 事件 listener 被綁定到整個文檔的根節點上.
當事件被觸發, 瀏覽器會給出一個觸發的目標的 DOM 節點.
爲了在 DOM 的層級傳播事件, React 不會迭代 virtual DOM 的層級.
而是, 咱們依靠每一個 React component 各自獨立的 id 來編碼這個層級.
咱們能經過簡單的字符串操做來獲取全部父級 component 的父級內容.
把事件 listener 存儲在 hash map 當中, 咱們發現性能比放到 virtual DOM 還要好.
這個例子展現了, 當一個事件廣播到整個 virtual 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);
瀏覽器爲每一個事件和每一個 listener 建立一個新的事件對象.
這個事件對象的屬性很不錯, 你能獲取到事件的引用, 甚至修改掉.
然而這也意味着高額的內存分配.
React 在啓動時就爲那些對象分配了一個內存池.
任什麼時候候須要用到事件對象, 就能夠從這個內存池進行復用.
這一點很是顯著地減輕了垃圾回收的負擔.
你調用 component 的 setState
方法的時候, React 將其標記爲 dirty.
到每個事件循環結束, React 檢查全部標記 dirty 的 component 從新繪製.
這裏的"合併操做"是說, 在一個事件循環當中, DOM 只會被更新一次.
這個特性是構建高性能應用的關鍵, 並且用一般的 JavaScript 代碼難以實現.
而在 React 應用裏, 你默認就能實現.
調用 setState
方法時, component 會從新構建包括子節點的 virtual DOM.
若是你在根節點調用 setState
, 整個 React 的應用都會被從新渲染.
全部的 component, 即使沒有更新, 都會調用他們的 render
方法.
這個聽起來可怕, 性能像是很低, 但實際上咱們不會觸碰真實的 DOM, 運行起來沒那樣的問題.
首先, 咱們討論的是展現用戶界面. 由於屏幕空間有限, 一般你須要一次渲染成百上千條指令.
JavaScript 對於能處理的整個界面, 在業務邏輯上已經足夠快了.
另外一點, 在寫 React 代碼時, 每當有數據更新, 你不是都調用根節點的 setState
.
你會在須要接收對應更新的 component 上調用, 或者在上面的幾個 component.
你不多要一直到根節點上. 就是說界面更新只出如今用戶產生交互的局部.
最後, 你還有可能去掉一些子樹的從新渲染.
若是你在 component 上實現如下方法的話:
boolean shouldComponentUpdate(object nextProps, object nextState)
根據 component 的前一個和下一個 props/state
,
你能夠告訴 React 這個 component 沒有更新, 也不須要從新繪製.
實現得好的話, 能夠帶來巨大的性能提高.
要用這個方法, 你要可以對 JavaScript Object 進行比對.
這件有不少細節的因素, 好比對比應該是深度的仍是淺層的,
若是要深的, 咱們是用不可變數據結構, 仍是進行深度拷貝...
並且你要注意, 這個函數每次都會被調用, 因此你要確保運行起來花的時間更少,
比 React 的作法時間少, 還有比計算 component 須要的時間少,
即使從新繪製並非必要的.
幫助 React 變快的技術並不新穎. 長久以來, 咱們到知道觸碰 DOM 是費時的,
你應該合併處理讀和寫的操做, 事件代理會更快...
人們仍是會常常討論他們, 由於在實際當中用 JavaScript 進行實現非常挺難的.
React 突出的一個緣由是這些優化默認就啓動了.
這就讓你避免掉不當心把 app 寫得很慢.
React 消耗性能的模型很簡單, 很好理解: 每次調用 setState
會從新計算整個子樹.
若是你想要提升性能, 儘可能少調用 setState
,
還有用 shouldComponentUpdate
減小大的子樹的從新計算.