首頁  >  文章  >  web前端  >  React中虛擬dom與diff演算法的講解(附程式碼)

React中虛擬dom與diff演算法的講解(附程式碼)

不言
不言轉載
2018-10-18 17:04:293342瀏覽

這篇文章帶給大家的內容是關於React中虛擬dom與diff演算法的講解(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

虛擬dom

Jsx 表面寫的是html,其實內部執行的是一段js
createElement

React.createElement(
  type,
  [props],
  [...children]
)

createElement把這個樹狀結構,存在記憶體裡面
Jsx最後以這樣的一個個物件遞歸的存在記憶體中,執行diff演算法。

React中虛擬dom與diff演算法的講解(附程式碼)

多層結構

React中虛擬dom與diff演算法的講解(附程式碼)

#簡單的createElement實作

React中虛擬dom與diff演算法的講解(附程式碼)

#reactElement - 產生的是物件來描述這個節點

React中虛擬dom與diff演算法的講解(附程式碼)

react diff


React中虛擬dom與diff演算法的講解(附程式碼)


React中虛擬dom與diff演算法的講解(附程式碼)



  • #計算一棵樹形結構轉換成另一棵樹形結構的最少操作,是一個複雜且值得研究的問題。傳統diff 演算法透過循環遞歸對節點進行依序對比,效率低下,演算法複雜度達到O(n^3)
  • react diff策略

React中虛擬dom與diff演算法的講解(附程式碼)

######## ##Web UI 中DOM 節點跨層級的行動操作特別少,可以忽略不計。 ############擁有相同類別的兩個元件將會產生相似的樹狀結構,擁有不同類別的兩個元件將會產生不同的樹狀結構。 ############對於同一層級的一組子節點,它們可以透過唯一 id 來區分。 ###############tree diff#########基於策略一,對樹進行分層比較,兩棵樹只會對同一層次的節點進行比較。 ### React 透過 updateDepth 對 Virtual DOM 樹進行層級控制,同一個父節點下的所有子節點。 #####################什麼是 DOM 節點跨層級的移動操作? ######A 節點(包括其子節點)整個被移動到D 節點下########################如果出現了DOM 節點跨層級的移動操作,React diff 會有怎樣的表現呢? ######React 只會簡單的考慮同層級節點的位置變換,而對於不同層級的節點,只有建立和刪除操作。 #########當根節點發現子節點中A 消失了,就會直接銷毀A;當D 發現多了一個子節點A,則會建立新的A(包括子節點)作為其子節點。此時,React diff 的執行情況:create A -> create B -> create C -> delete A。 ######注意:######在開發元件時,保持穩定的 DOM 結構會有助於效能的提升。例如,可以透過 CSS 隱藏或顯示節點,而不是真的移除或新增 DOM 節點。 ############component diff#########依據策略二############如果是相同類型的元件,請依照原始策略繼續比較virtual DOM tree。 ############如果不是,則將該元件判斷為 dirty component,從而替換整個元件下的所有子節點。 ############對於同一類型的元件,有可能其Virtual DOM 沒有任何變化,如果能夠確切的知道這點那可以節省大量的diff 運算時間,因此React 允許使用者透過shouldComponentUpdate( ) 來判斷該組件是否需要進行diff。 ############React 判斷D 和G 是不同類型的元件,就不會比較二者的結構,而是直接刪除component D,重新建立component G 以及其子節點,即使D和G的結構很相似########################element diff#########當節點處於同一層級時,React diff提供了三種節點操作,分別為:INSERT_MARKUP(插入)、MOVE_EXISTING(移動)和REMOVE_NODE(刪除)。 ###
  • INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。

  • MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。

  • REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。

eg: 新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。

React中虛擬dom與diff演算法的講解(附程式碼)

带来的问题:都是相同的节点,但由于位置发生变化,导致需要进行繁杂低效的删除、创建操作,其实只要对这些节点进行位置移动即可

react优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分

React中虛擬dom與diff演算法的講解(附程式碼)

优化后diff实现:

  1. 对新集合的节点进行循环遍历,通过唯一 key 可以判断新老集合中是否存在相同的节点

  2. 如果存在相同节点,则进行移动操作,但在移动前需要将当前节点在老集合中的位置child._mountIndex与lastIndex(访问过的节点在老集合中最右的位置即最大的位置)进行比较,if (child._mountIndex

分析:

element  _mountIndex  lastIndex  nextIndex  enqueueMove
B        1            0          0          false
A        0            1          1          true
D        3            1          2          false
C        2            3          3          true

step:

从新集合中取得 B,判断老集合中存在相同节点 B
B 在老集合中的位置 B._mountIndex = 1
初始 lastIndex = 0
不满足 child._mountIndex 更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex) lastIndex更新为1
将 B 的位置更新为新集合中的位置prevChild._mountIndex = nextIndex,此时新集合中 B._mountIndex = 0,nextIndex++

以上主要分析新老集合中存在相同节点但位置不同时,对节点进行位置移动的情况,如果新集合中有新加入的节点且老集合存在需要删除的节点,那么 React diff 又是如何对比运作的呢?

React中虛擬dom與diff演算法的講解(附程式碼)

    element  _mountIndex  lastIndex  nextIndex  enqueueMove
    B        1            0          0          false
    E        no exist
    C        2            1          2          false
    A        0            2          3          true

step

新建:从新集合中取得 E,判断老集合中不存在相同节点 E,则创建新节点 E
    lastIndex不做处理
    E 的位置更新为新集合中的位置,nextIndex++
删除:当完成新集合中所有节点 diff 时,最后还需要对老集合进行循环遍历,判断是否存在新集合中没有但老集合中仍存在的节点,发现存在这样的节点 D,因此删除节点 D

react diff的问题

理论上 diff 应该只需对 D 执行移动操作,然而由于 D 在老集合的位置是最大的,导致其他节点的 _mountIndex

React中虛擬dom與diff演算法的講解(附程式碼)

建议:在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

总结:

  • React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;

  • React 通过分层求异的策略,对 tree diff 进行算法优化;

  • React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;

  • React 通过设置唯一 key的策略,对 element diff 进行算法优化;

  • 建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;

  • 建議,在開發過程中,盡量減少類似將最後一個節點移動到列表首部的操作,當節點數量過大或更新操作過於頻繁時,在一定程度上會影響React 的渲染效能。

#

以上是React中虛擬dom與diff演算法的講解(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除