網站首頁 編程語言 正文
React虛擬DOM機制
- 虛擬DOM本質上是JavaScript對象,是對真實DOM的抽象
- 狀態變更時,記錄新樹和舊樹的差異
- 最后把差異更新到真正的dom中
React引入了虛擬DOM(Virtual DOM)的機制:在瀏覽器端用Javascript實現了一套DOM API。
基于React進行開發時所有的DOM構造都是通過虛擬DOM進行,每當數據變化時,React都會重新構建整個DOM樹,然后React將當前整個DOM樹和上一次的DOM樹進行對比,得到DOM結構的區別,然后僅僅將需要變化的部分進行實際的瀏覽器DOM更新。
而且React能夠批量處理虛擬DOM的刷新,在一個事件循環(Event Loop)內的兩次數據變化會被合并,例如你連續的先將節點內容從A變成B,然后又從B變成A,React會認為UI不發生任何變化。
盡管每一次都需要構造完整的虛擬DOM樹,但是因為虛擬DOM是內存數據,性能是極高的,而對實際DOM進行操作的僅僅是Diff部分,因而能達到提高性能的目的。這樣,在保證性能的同時,開發者將不再需要關注某個數據的變化如何更新到一個或多個具體的DOM元素,而只需要關心在任意一個數據狀態下,整個界面是如何Render的。
總之一句話:根據 React 的設計,所有的 DOM 變動,都先在虛擬 DOM 上發生,然后再將實際發生變動的部分,反映在真實 DOM上
React diff 算法
diff 算法作為Virtual DOM的加速器,其算法的改進優化是React整個界面渲染的基礎和性能的保障,同時也是React源碼中最神秘的,最不可思議的部分。
1. 傳統 diff 算法
計算一棵樹形結構轉換為另一棵樹形結構需要最少步驟,如果使用傳統的diff算法通過循環遞歸遍歷節點進行對比,其復雜度要達到O(n^3),其中n是節點總數,效率十分低下,假設我們要展示1000個節點,那么我們就要依次執行上十億次的比較。
下面附上一則簡單的傳統diff算法:
let result = []; // 比較葉子節點 const diffLeafs = function (beforeLeaf, afterLeaf) { // 獲取較大節點樹的長度 let count = Math.max(beforeLeaf.children.length, afterLeaf.children.length); // 循環遍歷 for (let i = 0; i < count; i++) { const beforeTag = beforeLeaf.children[i]; const afterTag = afterLeaf.children[i]; // 添加 afterTag 節點 if (beforeTag === undefined) { result.push({ type: "add", element: afterTag }); // 刪除 beforeTag 節點 } else if (afterTag === undefined) { result.push({ type: "remove", element: beforeTag }); // 節點名改變時,刪除 beforeTag 節點,添加 afterTag 節點 } else if (beforeTag.tagName !== afterTag.tagName) { result.push({ type: "remove", element: beforeTag }); result.push({ type: "add", element: afterTag }); // 節點不變而內容改變時,改變節點 } else if (beforeTag.innerHTML !== afterTag.innerHTML) { if (beforeTag.children.length === 0) { result.push({ type: "changed", beforeElement: beforeTag, afterElement: afterTag, html: afterTag.innerHTML }); } else { // 遞歸比較 diffLeafs(beforeTag, afterTag); } } } return result; }
2. react diff 算法
1. diff 策略
下面介紹一下react diff算法的3個策略
- Web UI 中DOM節點跨層級的移動操作特別少,可以忽略不計
- 擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構。
- 對于同一層級的一組子節點,它們可以通過唯一id進行區分。
對于以上三個策略,react 分別對 tree diff, component diff, element diff 進行算法優化。
2. tree diff
基于策略一,WebUI中DOM節點跨層級的移動操作少的可以忽略不計,React對Virtual DOM樹進行層級控制,只會對相同層級的DOM節點進行比較,即同一個父元素下的所有子節點,當發現節點已經不存在了,則會刪除掉該節點下所有的子節點,不會再進行比較。這樣只需要對DOM樹進行一次遍歷,就可以完成整個樹的比較。復雜度變為O(n);
疑問:當我們的DOM節點進行跨層級操作時,diff 會有怎么樣的表現呢?
如下圖所示,A節點及其子節點被整個移動到D節點下面去,由于React只會簡單的考慮同級節點的位置變換,而對于不同層級的節點,只有創建和刪除操作,所以當根節點發現A節點消失了,就會刪除A節點及其子節點,當D發現多了一個子節點A,就會創建新的A作為其子節點。?
此時,diff的執行情況是:
createA-->createB-->createC-->deleteA
由此可以發現,當出現節點跨層級移動時,并不會出現想象中的移動操作,而是會進行刪除,重新創建的動作,這是一種很影響React性能的操作。因此官方也不建議進行DOM節點跨層級的操作。
3. component diff
- React是基于組件構建應用的,對于組件間的比較所采用的策略也是非常簡潔和高效的。
- 如果是同一個類型的組件,則按照原策略進行Virtual DOM比較。
- 如果不是同一類型的組件,則將其判斷為dirty component,從而替換整個組價下的所有子節點。
- 如果是同一個類型的組件,有可能經過一輪Virtual DOM比較下來,并沒有發生變化。如果我們能夠提前確切知道這一點,那么就可以省下大量的diff運算時間。因此,React允許用戶通過shouldComponentUpdate()來判斷該組件是否需要進行diff算法分析。
如下圖所示,當組件D變為組件G時,即使這兩個組件結構相似,一旦React判斷D和G是不用類型的組件,就不會比較兩者的結構,而是直接刪除組件D,重新創建組件G及其子節點。
雖然當兩個組件是不同類型但結構相似時,進行diff算法分析會影響性能,但是畢竟不同類型的組件存在相似DOM樹的情況在實際開發過程中很少出現,因此這種極端因素很難在實際開發過程中造成重大影響。?
4. element diff
當節點屬于同一層級時,diff提供了3種節點操作,分別為INSERT_MARKUP(插入),MOVE_EXISTING(移動),REMOVE_NODE(刪除)。
-
INSERT_MARKUP
:新的組件類型不在舊集合中,即全新的節點,需要對新節點進行插入操作。 -
MOVE_EXISTING
:舊集合中有新組件類型,且element是可更新的類型,這時候就需要做移動操作,可以復用以前的DOM節點。 -
REMOVE_NODE
:舊組件類型,在新集合里也有,但對應的element不同則不能直接復用和更新,需要執行刪除操作,或者舊組件不在新集合里的,也需要執行刪除操作。?
總結
通過diff策略,將算法從O(n^3)簡化為O(n)。
分層求異,對tree diff進行優化。
分組件求異,相同類生成相似樹形結構、不同類生成不同樹形結構,對component diff進行優化。
設置key,對element diff進行優化。
盡量保持穩定的DOM結構、避免將最后一個節點移動到列表首部、避免節點數量過大或更新過于頻繁。
最后
原文鏈接:https://blog.csdn.net/huangpb123/article/details/84947475
相關推薦
- 2022-10-04 python?numpy庫中數組遍歷的方法_python
- 2023-12-16 六個開發者必知必會的Git命令
- 2022-10-01 react使用useState修改對象或者數組的值無法改變視圖的問題_React
- 2023-05-22 python中decimal模塊的用法_python
- 2022-06-12 Python中property屬性的用處詳解_python
- 2022-09-20 Redis深入了解內存淘汰與事務操作_Redis
- 2022-08-31 .Net插件框架Managed?Extensibility?Framework簡介_實用技巧
- 2022-04-19 基于HarmonyOS 的ArkUI編寫的社區類app(一)----隱私服務條款界面
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支