聊一聊React中虛擬DOM


1. 什么是虛擬 DOM

在 React 中實際上是 render 函數中return 的內容會生成 DOM,return 中的內容由兩部分組成,一部分是 JSX ,另一部分就是 state 中的數據,所以簡單來講,在 React 中 JSX 結合 state 就生成了 DOM。

現在拋開虛擬 DOM 不談,如果讓我們去實現 React 中當數據發生變化時如何操作 DOM 來實現頁面內容的變化,我們會怎樣去實現?

第一種方案:

1)JSX + state 生成真實的 DOM,並顯示在頁面上

2)state 發生變化

3)此時 JSX + state 再次結合生成新的真實的 DOM

4)新的 DOM 直接替換掉原來的 DOM

這樣頁面會發生變化,但是生成真實的 DOM 和在頁面上再重新加載新的 DOM 都比較耗性能。

第二種方案:

1)JSX + state 生成真實的 DOM,並顯示在頁面上

2)state 發生變化

3)此時 JSX + state 再次結合生成新的真實的 DOM

4)新的 DOM 和原始的 DOM 作對比,找出差異

5)利用找出的差異,替換掉頁面上原始 DOM 的相應部分

此時頁面也會發生變化,和方案一相比多了對比步驟但是只需要替換掉原始DOM的一部分即可,綜合來說,方案二要優於方案一。

第三種方案

1)JSX + state 生成虛擬 DOM(虛擬 DOM 就是一個 JS 對象,用它來描述真實 DOM)

例如下面這段代碼:

<div id='abc'>item</div>

注意上面的 divspan 標簽時 JSX 語法,並不是真實的 DOM,這里是先生成虛擬 DOM ,然后再下一步的時候才用虛擬 DOM 生成真實的 DOM,由 JSX 到真實的 DOM 中間有一個虛擬 DOM。

JSX -> 虛擬DOM(JS對象) -> 真實DOM

也就是說,JSX 需要先轉換為 JS 對象,然后再轉換為真實的 DOM。

生成的虛擬 DOM 為

['div',{id: 'abc'}, 'item']

虛擬 DOM 的格式為

['標簽名',標簽屬性對象,子標簽]

那么 <div id='abc'>item</div> 是如何轉化為 JS 對象的呢?

實際上在 React 中上面這樣寫就相當於下面這樣寫:

React.createElement('div', {id: 'abc'}, 'item');

那么實際上就算是沒有 JSX 語法通過上面這樣寫也是可以的,但是會非常不方便。

2)用虛擬 DOM 的結構生成真實的 DOM 顯示在頁面上。

3)JSX + state 生成新的虛擬 DOM

4)兩個虛擬 DOM 進行對比,找出差異

5)根據差異直接修改替換頁面上的 DOM

虛擬 DOM 是一個 JS 對象,生成一個虛擬 DOM 比生成一個真實的 DOM 結構要容易省時地多,而且兩個虛擬 DOM(JS 對象) 之間的對比也比較簡單,所以方案三最佳。

React 中使用的也是第三種方案的思想。

2. 虛擬 DOM 的優點

那么虛擬DOM的優點到底有哪些呢?

1)性能提升

這一點通過上面的比較就可以看得出來

2)使得跨端應用得以實現,例如原生應用。

React Native 能夠做原生應用虛擬 DOM 是很重要的一方面,原生應用中是沒有 DOM 這個概念的,DOM 是瀏覽器中存在的,但是有了虛擬 DOM(JS 對象) 之后,在原生應用中就可以將虛擬 DOM(JS 對象) 轉換為一些原生應用中能夠支持的原生組件在原生應用中顯示。

3. 虛擬 DOM 的對比

使用虛擬 DOM 時很重要的一個步驟就是兩個虛擬 DOM 之間的比較,那么怎樣去進行比較呢?

React 中采用 diff 算法,簡單來說主要有以下三個方面:

1)當短時間內連續調用多次 setState 時,React 只會進行一次虛擬 DOM 的比對。

我們知道當 state 或者 props 發生變化時,頁面會發生變化,實際上 props 的變化也是因為父組件 state 的變化,所以當頁面發生變化時實際上是調用 setState 導致數據發生變化變化時。當短時間內連續調用多次 setState 時,如果每次都進行一次虛擬 DOM 的比對,那么性能會比較低,反之多次調用 setState 只進行一次虛擬 DOM 的比對會提升性能。這也是為什么 setState 要設置成異步的原因,因為如果同步的話當執行完一次 setState 時就會發生一次虛擬 DOM 的比對。(同步是順序立即執行,異步是當所有的同步程序執行完后再執行)

2)在比較虛擬 DOM 時采用逐層同層比較,當上一層出現差異時,那么下面的各層就不需要再比較了,下面各層的 DOM 都將被新的 DOM 替換。

這樣做看起來,復用性不是很好,因為下面各層有可能會有許多相同的 DOM。但是這樣做會使得比較算法非常簡單,比較的速度非常快。

3)設置 key 值

假設現在有一個數組 [a, b, c] 遍歷每一項顯示在頁面上,現在數組發生變化將第一項 a 刪掉,如果沒有 key 值,數組 [b, c] 無法和原數組進行比對,例如 b 到底和原數組的哪一個進行比較呢?

但是現在假設有了 key 值,原數組中 a 的 key 值是 a,b 的 key 值是 b,c 的 key 值是 c。刪除 a 之后,通過 key 值,b 的 key 值 b 在原數組中找到 b,說明 b 沒有發生變化,c 同理也沒有發生變化,但是原數組中的 a 在新數組中並沒有找到,說明新數組中將 a 刪掉了,所以在操作頁面時將 a 刪掉即可。

這里有一點需要注意的是,key 值一定要選不能變化的,利用數組的索引來做 key 值就不可取。還是以上面為例進行說明。原數組的 a 的 key 值是 0,b 的 key 值是 1,c 的 key 值是 2,刪掉 a 后,新數組的 b 的 key 值是 0,c 的 key 值是 1,經過比對原數組的 a 和新數組的 b key 值相同,虛擬 DOM 會認為它們是相同的,沒有差異,但是實際上它們是不同的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM