DOM-DIFF

在 React17+ 中 DOM-DIFF 就是根據(jù)老的 fiber 樹和最新的 JSX 對比生成新的 fiber 樹的過程

正常樹的 diff 算法時(shí)間復(fù)雜度是 O(n^3),但是這個(gè)時(shí)間復(fù)雜度太高,所以 React 進(jìn)行了一些優(yōu)化

React 中的優(yōu)化規(guī)則

  • 只對同級節(jié)點(diǎn)進(jìn)行對比,如果 DOM 節(jié)點(diǎn)跨層級移動(dòng),則 React 不會復(fù)用
  • 不同類型的元素會產(chǎn)出不同的結(jié)構(gòu),會銷毀老的結(jié)構(gòu),創(chuàng)建新的結(jié)構(gòu)
  • 可以通過 key 標(biāo)識移動(dòng)的元素

單節(jié)點(diǎn),新的元素只有一個(gè)的情況

1. type 不同

<div>
  // 在調(diào)和階段,需要把這個(gè)老節(jié)點(diǎn)標(biāo)記為刪除
  <h1 key="null">h1</h1> 
</div>

更新為:

<div>
  // 生成新的 fiber 節(jié)點(diǎn)并標(biāo)記為插入
  <h2 key="null">h2<h2>
</div>

在 commit 階段,會執(zhí)行兩個(gè)操作:

  • div.removeChild(h1)
  • div.appendChild(h2)

2. type 和 key 都不同

<div>
  // h1 標(biāo)記為刪除
  <h1 key="h1">h1</h1>
  <h2 key="h2">h2</h2>
  <h3 key="h3">h3</h2>
</div>

更新為:

<div>
  <h2 key="h2">h2</h2>
</div>

對于這個(gè)案例,先拿 h2 和 h1 匹配,發(fā)現(xiàn)不能復(fù)用,把 h1 標(biāo)記為刪除,匹配到 h2,可以復(fù)用,然后把剩下的節(jié)點(diǎn)全部刪除

  • div.removeChild(h1)
  • div.removeChild(h3)

3. key 相同 type 也相同

<div>
  <h1 key="h1">h1</h1>
</div>

更新為:

<div>
  <h1 key="h1">h1 - new</h1>
</div>

如果對比后發(fā)現(xiàn)新老節(jié)點(diǎn)一樣的,那么會復(fù)用老節(jié)點(diǎn),復(fù)用老節(jié)點(diǎn)的 DOM 元素和 Fiber 對象

再看屬性有無變更,如果有變化,則會把此 Fiber 節(jié)點(diǎn)標(biāo)記為更新

h1.innerHTML = 'h1 - new'

4. key 相同但是 type 不同,直接刪除所有老節(jié)點(diǎn)

<div>
  <h1 key="h1">h1</h1>
  <h2 key="h2">h2</h2>
</div>

更新為:

<div>
  <p key="h1">p</p>
</div>

如果 key 相同,但是 type 不同,則不再進(jìn)行后續(xù)對比了,因?yàn)?key 一樣代表是同一個(gè)元素,如果 type 不一樣說明節(jié)點(diǎn)已經(jīng)發(fā)生改變,直接把老節(jié)點(diǎn)全部刪掉,插入新節(jié)點(diǎn)即可

key 相同表示這是同一個(gè)元素

  • div.removeChild(h1)
  • div.removeChild(h2)
  • div.appendChild(p)

多節(jié)點(diǎn)

如果有多個(gè)節(jié)點(diǎn),節(jié)點(diǎn)有可能會更新,刪除,新增,多節(jié)點(diǎn)的時(shí)候會經(jīng)過兩輪遍歷

第一輪遍歷主要處理節(jié)點(diǎn)的更新,更新包括屬性和類型的更新

第二輪遍歷主要處理節(jié)點(diǎn)的新增、刪除和移動(dòng)

移動(dòng)時(shí)的原則是盡量少量的移動(dòng),如果必須有一個(gè)要?jiǎng)樱匚桓叩牟粍?dòng),地位低的動(dòng)

對比,都可復(fù)用,只需更新

<ul>
  <li key="A">A</li>
  <li key="B">B</li>
  <li key="C">C</li>
  <li key="D">D</li>
</ul>

更新為:

<ul>
  <li key="A">A - new</li>
  <li key="B">B - new</li>
  <li key="C">C - new</li>
  <li key="D">D - new</li>
</ul>

最后會得到一個(gè)操作序列:

  1. 更新 A
  2. 更新 B
  3. 更新 C
  4. 更新 D

key 相同,type 不同

<ul>
  <li key="A">A</li> // Fiber 節(jié)點(diǎn)
  <li key="B">B</li>
  <li key="C">C</li>
  <li key="D">D</li>
</ul>

更新為:

<ul>
  <div key="A">A - new</div> // JSX 節(jié)點(diǎn)
  <li key="B">B - new</li>
  <li key="C">C - new</li>
  <li key="D">D - new</li>
</ul>

第一步比較第一個(gè)節(jié)點(diǎn),發(fā)現(xiàn)不能復(fù)用,老的標(biāo)記刪除,新的標(biāo)記插入

后面的都可復(fù)用

最后結(jié)論是:

刪除老的 li A,插入 divA,更新 B C D

DOM-DIFF 是一個(gè)對比老的 Fiber 鏈表和新的 JSX 數(shù)組,生成新的 Fiber 鏈表的過程

老的是 Fiber,新的是 JSX

key 不同退出第一輪循環(huán)

因?yàn)槿绻?key 不一樣已經(jīng)發(fā)生位置變化了

<ul>
  <li key="A" style={oldStyle}>A</li>
  <li key="B" style={oldStyle}>B</li>
  <li key="C" style={oldStyle}>C</li>
  <li key="D" style={oldStyle}>D</li>
  <li key="E" style={oldStyle}>E</li>
  <li key="F" style={oldStyle}>F</li>
</ul>

更新為:

<ul>
  <li key="A" style={oldStyle}>A - new</li>
  <li key="C" style={oldStyle}>C - new</li>
  <li key="E" style={oldStyle}>E - new</li>
  <li key="B" style={oldStyle}>B - new</li>
  <li key="G" style={oldStyle}>G</li>
</ul>

首先第一輪循環(huán)

oldA 和 newA 比較,一樣,可以復(fù)用,更新 A

oldB 和 newC 比較,key 不一樣,跳出第一輪循環(huán)

進(jìn)行第二輪循環(huán)

建一個(gè) map 對象 let map = {'B': B, 'C': C, ' 'D': D, 'E': E, 'F': F}(不處理 A,因?yàn)?A 在第一輪循環(huán)已經(jīng)處理完了) 表示還沒有復(fù)用的節(jié)點(diǎn)

繼續(xù)遍歷新節(jié)點(diǎn),到 newC 了

newC 節(jié)點(diǎn)在 map 找有沒有 key 為 C 的 fiber 節(jié)點(diǎn)

如果有,并且可以復(fù)用(Fiber 和 DOM 可以復(fù)用),說明只是位置變了,把 oldC 標(biāo)標(biāo)記為更新

  • lastPlacedIndex 表示最大的不需要?jiǎng)拥睦系墓?jié)點(diǎn)的索引

  • oldC 的 oldIndex 是 2,oldIndex > lastPlacedIndex,所以 C 不需要?jiǎng)?/p>

  • lastPlacedIndex = 2

  • map 中刪除 C

再看 newE

  • 在 map 里面找 E,找到了 oldE,oldIndex 是 4,lastPlacedIndex < 4,E 也不動(dòng)

  • lastPlacedIndex = 4

  • map 中刪除 E

到 newB 了,找到 oldB,oldIndex = 1 < lastPlacedIndex,oldB 需要往后挪

newG 不在 map 里面,標(biāo)記為插入

等 JSX 數(shù)組全部遍歷完之后,把 map 里面全部 fiber 節(jié)點(diǎn)標(biāo)記為刪除,也就是 oldE 和 oldF

在 react 里面,每一個(gè)操作都有一個(gè)權(quán)重(上圖中那個(gè)表),每種操作都有權(quán)重,這些都記錄在 effectList 里面

每次 DOM Diff 會生成一個(gè) effectList,在 commitMutationEffects(ReactFiberWorkLoop.old.js) 會使用,根據(jù) effectList 進(jìn)行操作

另外,那個(gè)優(yōu)先級在 ReactFiberFlags.js 里面可以找到

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 平常開發(fā)中總是聽到這個(gè)詞,虛擬dom,但從來未對這個(gè)詞好好地去學(xué)習(xí)。今天學(xué)習(xí)了一位作者分享的文章。覺得受益匪淺。于...
    小小小小的人頭閱讀 1,036評論 1 5
  • 面試中如果你簡歷上寫了掌握React,那面試官肯定會讓你說一下dom-diff算法的過程,那么今天就實(shí)現(xiàn)一個(gè)簡單的...
    前端研修室閱讀 2,099評論 0 1
  • 動(dòng)態(tài)路由:如果需要獲取動(dòng)態(tài)路由id,建議使用props方式: 編程式導(dǎo)航: $router有兩種用法,第一種直接添...
    zxhnext閱讀 904評論 0 2
  • 假如我們想自己實(shí)現(xiàn)一個(gè)React,簡單底層實(shí)現(xiàn): 1.state數(shù)據(jù)2.JSX 模板3.數(shù)據(jù) + 模板 結(jié)合,生成...
    燈火葳蕤234閱讀 796評論 0 1
  • 之前學(xué)習(xí)React的時(shí)候看到一篇文章《Build Your Own React》, 不論從質(zhì)量還是更新速度上, 都...
    風(fēng)雅歡樂閱讀 437評論 0 0

友情鏈接更多精彩內(nèi)容