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