React虛擬Dom渲染演算法
React提供了一系列宣告性的API介面,因此在使用時不必擔心每次庫的更新會修改API介面。這樣可以降低編寫應用的複雜度,但是帶來的問題是無法很好的理解React是如何實現這些功能的。這篇文章會介紹React的差異比對演算法——“融合演算法”是如何執行的。
差異匹配演算法實現的前提
我們先來看看第一個值得關注的我問題: render()
方法的作用是建立React元素的樹形結構,當state或props發生更新後, render()
會返回一個與之前有差異的結構樹。在這個機制下,React需要弄清楚如何匹配最近的樹並有效的更新UI。
針對以上問題,有一些通用的演算法可供參考,比如比對2顆樹的差異,在前一個顆樹的基礎上生成最小操作樹,但是這個演算法的時間複雜度為n的三次方=O(n*n*n),當樹的節點較多時,這個演算法的時間代價會導致演算法幾乎無法工作。
假設在我們使用React時,一共使用了1000個Dom標籤元素,那麼使用上面的演算法,我們要比對數億次才能得到比對的結果,根本不可能在一個瀏覽器中短時間完成。React實現了一個計算複雜度是O(n)的演算法來解決這個問題,這個演算法基於2個假設:
- 不同型別的2個標籤元素產生不同的樹。
- 開發人員可以為不同的子節點在渲染之前設定一個“key”屬性值。
差異演算法
對於2顆有差異的樹,React首先比對2顆樹的根節點。根據跟節點的型別是否相同,演算法接下來會執行不同的操作。
Types不一樣
一旦2棵樹之間的根元素型別不一樣,React會直接移除舊的樹並構建出新的樹。例如從 <a>
變更為 <img>
、 <Article>
變更為 <Comment>
、 <Button>
變更為 <div>
,所有的這些變化都會導致整顆樹重構。
重構一棵新的樹時,所有的舊節點都會移除。元件的componentWillUnmount()
方法會被呼叫。 然後到構建完成之後新的Dom會替換原來的Dom。此時元件的componentWillMount()
和componentDidMount()
會依次被呼叫。舊樹Dom上的所有狀態都會丟失。
根據這個特性,根節點之後的所有元件都會解除安裝並重建,狀態也會隨之改變。例如下面2個元件對比:
<div>
<Counter />
</div>
<span>
<Counter />
</span>
Counter
元件會被銷燬並重新安裝一個新的元件。
Dom元素擁有相同的型別
當比較React元素為相同型別時,React會檢視元素上的屬性來比對。比對之後,React會保持的Dom節點不改變然後僅僅更新不同的屬性值,例如:
<div className="before" title="stuff" />
<div className="after" title="stuff" />
在比對這2個元素之後,React知道僅僅需要修改當前Dom的className
。在更新style
時,React同樣知道僅僅需要更新修改部分即可。例如:
<div style={{color: `red`, fontWeight: `bold`}} />
<div style={{color: `green`, fontWeight: `bold`}} />
在轉換這2個元件時,React知道僅僅需要修改color的樣式,而fontWeight不必發生變動。
在處理完當前Dom節點後,React依次對子節點進行遞迴。
元件元素擁有相同的型別
當一個元件發生更新後,例項依然是原來的例項,所以狀態還是以前的狀態。React通過屬性值(props)的更新來影響需要更新元件,此時元件例項的 componentWillReceiveProps()
和 componentWillUpdate()
方法會被呼叫。
然後, render()
方法會被呼叫並返回一個Dom,差異演算法會遞迴比對之前返回Dom的差異。
遞迴子元素
預設情況下,在遞迴子元素的Dom節點時,React同時對2個子元素列表進行迭代比對,如果發現差異都會產生一個突變(關於突變的概念請見React學習第六篇效能優化介紹不可變資料結構部分)。
例如,當增加一個元素在子元素的隊尾,這2顆樹的轉換效率很高:
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React先匹配 <li>first</li>
2棵樹,然後再匹配 <li>second</li>
。最後直接就新增 <li>third</li>
節點。
如果程式碼按下面的方式修改2顆樹,執行的效率相對較差:
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React會突變修改所有的子節點,最終 <li>Duke</li>
and <li>Villanova</li>
會被重新渲染。所以這種方式會帶來很大的效率問題。
Keys
為了解決上面的問題,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可以知道key=`2014`的節點是一個新值另外2個節點僅僅需要移動一下位置。
在實際使用中,key值並不難找。在常規業務中,很多列表都自然包含業務相關的ID了:
<li key={item.id}>{item.name}</li>
當無法使用業務ID時,也可以額外增加一個ID值來標記列表差異,比如根據要使用的資料生成一個hash值,React不需要key值全域性唯一,只需要在兄弟節點之間保持唯一即可。
最差情況下,你可以使用索引資料(0、1、2、….n)。使用索引需要注意的是,如果列表發生重新排序效率會很糟糕。
一些常見的問題
在使用React時需要謹記每次呼叫 render() 方法,它總會嘗試比對呼叫前後2棵樹是否一致。在某些極端情況下,雖然最終呈現效果並沒有發生多大的變化,但是有可能每一個簡單的操作都導致React全域性重新渲染(例如列表沒有Key)。
React在當前版本的實現中還存在一個問題,可以快捷的告知React子樹中某個節點的位置已經發生改變,但是無法告知React他移動到了什麼位置。因此在遇到這種情況時,演算法會重構整個子樹。這個問題告訴我們,如果遇到彈窗之類需要偶爾出現的元件,最好是通過隱藏屬性控制他,而非直接移除Dom。
React依賴啟發式演算法,如果本文開篇提到的2個基本假設不成立,那麼會導致演算法效率極差。
- 演算法不會嘗試匹配不同2個元件之間的子樹。如果編碼中發現2個元件之間有非常相似的輸出,應該嘗試將2個元件合併為一個型別的元件。在實際應用中,我們還沒發現這樣導致問題。
- 用作列表的key值最好是穩定、可預見、唯一的。易變的key值(比如由
Math.random()
方法生成的值)將會導致許多元件例項和Dom節點被非必要的重新建立,這會導致效能低下且子元件丟失已有的狀態。
相關文章
- REACT——虛擬DOMReact
- React虛擬dom和diff演算法React演算法
- 【React深入】深入分析虛擬DOM的渲染原理和特性React
- vue2.0的虛擬DOM渲染Vue
- React虛擬DOM的好處React
- React 的虛擬 DOM 和 Vue 的虛擬 DOM 有什麼區別?ReactVue
- React 虛擬Dom 轉成 真實Dom 實現原理React
- 基於虛擬DOM(Snabbdom)的迷你ReactReact
- 虛擬DOM與diff演算法演算法
- 虛擬DOM
- [譯] 基於虛擬DOM(Snabbdom)的迷你ReactReact
- [譯] React效能優化-虛擬Dom原理淺析React優化
- 談談虛擬dom和diff演算法演算法
- 詳解虛擬DOM與Diff演算法演算法
- 初探虛擬 DOM
- Vue虛擬DOMVue
- React原始碼閱讀:虛擬DOM的初始化React原始碼
- 虛擬DOM和Diff演算法 - 入門級演算法
- vue虛擬dom原理Vue
- 從零開始實現React(一):JSX和虛擬DOMReactJS
- [譯] 認識虛擬 DOM
- 什麼是虛擬DOM
- 虛擬Dom詳解 - (一)
- Vue 為什麼要用虛擬 DOM(Virtual DOM)Vue
- 通過編寫簡易虛擬DOM,來學習虛擬DOM 的知識!
- VirtualDOM----snabbdom虛擬dom庫 demo
- vue 快速入門 系列 —— 虛擬 DOMVue
- NEXT Story——虛擬人(三):渲染
- 虛擬dom優化效能的表現優化
- 測試用例 虛擬dom下載
- 瞭解react、vue的一大核心技術:虛擬DOM的實現原理ReactVue
- 淺談GPU虛擬化技術:GPU圖形渲染虛擬化GPU
- 前端優化反應:虛擬dom解釋前端優化
- 虛擬Dom與Diff的簡單實現
- 解密虛擬 DOM——snabbdom 核心原始碼解讀解密原始碼
- 實現一個簡單的虛擬DOM
- 實時雲渲染如何助力虛擬展廳
- 根據除錯工具看原始碼之虛擬dom(一)除錯原始碼