激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務(wù)器之家 - 編程語(yǔ)言 - JavaScript - React - react diff算法源碼解析

react diff算法源碼解析

2022-02-27 17:13zhangyu React

這篇文章主要介紹了react diff算法源碼解析的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用react,感興趣的朋友可以了解下

React中Diff算法又稱為調(diào)和算法,對(duì)應(yīng)函數(shù)名為reconcileChildren,它的主要作用是標(biāo)記更新過(guò)程中那些元素發(fā)生了變化,這些變化包括新增、移動(dòng)、刪除。過(guò)程發(fā)生在beginWork階段,只有非初次渲染才會(huì)Diff。

以前看過(guò)一些文章將Diff算法表述為兩顆Fiber樹(shù)的比較,這是不正確的,實(shí)際的Diff過(guò)程是一組現(xiàn)有的Fiber節(jié)點(diǎn)和新的由JSX生成的ReactElement的比較,然后生成新的Fiber節(jié)點(diǎn)的過(guò)程,這個(gè)過(guò)程中也會(huì)嘗試復(fù)用現(xiàn)有Fiber節(jié)點(diǎn)。

節(jié)點(diǎn)Diff又分為兩種:

  1. 單節(jié)點(diǎn)Diff —— ElementPortalstringnumber
  2. 多節(jié)點(diǎn)Diff —— ArrayIterator

以下React版本為17.0.1,代碼文件為ReactChildFiber.old.js

單節(jié)點(diǎn)Diff

單節(jié)點(diǎn)Diff比較簡(jiǎn)單,只有key相同并且type相同的情況才會(huì)嘗試復(fù)用節(jié)點(diǎn),否則會(huì)返回新的節(jié)點(diǎn)。

單節(jié)點(diǎn)大部分情況下我們都不會(huì)去賦值key,所以它們默認(rèn)為null,也是相同的。

reconcileSingleElement

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 單節(jié)點(diǎn)比較
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  // 當(dāng)前新的reactElement的key
  const key = element.key;
  // 當(dāng)前的child fiber節(jié)點(diǎn)
  let child = currentFirstChild;
  while (child !== null) {
    // key相同的情況才diff
    if (child.key === key) {
      switch (child.tag) {
        // ...
        default: {
          // 當(dāng)前fiber和reactElement的type相同時(shí)
          if (child.elementType === element.type) {
            // 刪除同級(jí)的其他節(jié)點(diǎn)
            deleteRemainingChildren(returnFiber, child.sibling);
            // 復(fù)用當(dāng)前child fiber
            const existing = useFiber(child, element.props);
            existing.ref = coerceRef(returnFiber, child, element);
            existing.return = returnFiber;
            // 返回可復(fù)用的child fiber
            return existing;
          }
          break;
        }
      }
      // 不匹配刪除節(jié)點(diǎn)
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      // key不同直接刪除節(jié)點(diǎn)
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }
 
  // 新的Fiber節(jié)點(diǎn)
  const created = createFiberFromElement(element, returnFiber.mode, lanes);
  created.ref = coerceRef(returnFiber, currentFirstChild, element);
  created.return = returnFiber;
  return created;
}

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

源碼中將多節(jié)點(diǎn)分為了數(shù)組節(jié)點(diǎn)和可迭代節(jié)點(diǎn)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (isArray(newChild)) {
  return reconcileChildrenArray(
    returnFiber,
    currentFirstChild,
    newChild,
    lanes,
  );
}
 
if (getIteratorFn(newChild)) {
  return reconcileChildrenIterator(
    returnFiber,
    currentFirstChild,
    newChild,
    lanes,
  );
}

對(duì)應(yīng)的Diff函數(shù)分別是reconcileChildrenArrayreconcileChildrenIterator。它們的核心Diff邏輯是相同的,所以只分析數(shù)組節(jié)點(diǎn)的Diff —— reconcileChildrenArray函數(shù)。

這一段的代碼比較長(zhǎng),但邏輯很清晰,從分割線分為兩輪遍歷。

  • 第一輪遍歷的是順序相同且key也相同的節(jié)點(diǎn),這些節(jié)點(diǎn)需要做更新操作。
  • 第二輪遍歷的是順序不同,可能key也不同的節(jié)點(diǎn),這些節(jié)點(diǎn)需要做新增、移動(dòng)或刪除操作。

第一輪遍歷只針對(duì)key和順序都相同的情況,這些key對(duì)應(yīng)的節(jié)點(diǎn)位置沒(méi)有發(fā)生改變,只需要做更新操作,一旦遍歷遇到key不同的情況就需要跳出循環(huán)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 舊節(jié)點(diǎn)
<li key="0"/>
<li key="1"/>
<li key="2"/>
// 新節(jié)點(diǎn)
<li key="0"/>
<li key="1"/>
<li key="5"/>
 
// key="5"不同,跳出遍歷
// 第一輪遍歷的節(jié)點(diǎn)
<li key="0"/>
<li key="1"/>
// <li key="2"/>和<li key="5"/>留在第二輪遍歷比較。

在第一輪遍歷完后也分為兩種情況。

  1. 新節(jié)點(diǎn)數(shù)量少于舊節(jié)點(diǎn)數(shù)量,這時(shí)候需要把多余的舊節(jié)點(diǎn)標(biāo)記為刪除。
  2. 新節(jié)點(diǎn)數(shù)量大于舊節(jié)點(diǎn)數(shù)量,這時(shí)候需要把多余的新節(jié)點(diǎn)標(biāo)記為新增。

第二輪遍歷針對(duì)key不同或順序不同的情況,可能情況如下:

?
1
2
3
4
5
6
7
8
9
10
// 舊節(jié)點(diǎn)
<li key="0"/>
<li key="1"/>
<li key="2"/>
// 新節(jié)點(diǎn)
<li key="0"/>
<li key="2"/>
<li key="1"/>
 
// 第二輪遍歷對(duì)比<li key="2"/>、<li key="1"/>這兩個(gè)節(jié)點(diǎn)

第二輪的遍歷會(huì)稍微復(fù)雜一點(diǎn),后文在細(xì)講。

詳細(xì)的代碼如下。

reconcileChildrenArray

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  lanes: Lanes,
): Fiber | null {
  // 函數(shù)返回的Fiber節(jié)點(diǎn)
  let resultingFirstChild: Fiber | null = null;
  let previousNewFiber: Fiber | null = null;
 
  // oldFiber為鏈表
  let oldFiber = currentFirstChild;
  // 用來(lái)判斷節(jié)點(diǎn)是否移動(dòng)
  let lastPlacedIndex = 0;
  let newIdx = 0;
  let nextOldFiber = null;
  // 第一輪遍歷,只遍歷key相同的節(jié)點(diǎn)
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    if (oldFiber.index > newIdx) {
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      // 每次循環(huán)舊的fiber節(jié)點(diǎn)都會(huì)指向兄弟元素也就是下次循環(huán)的fiber節(jié)點(diǎn)
      nextOldFiber = oldFiber.sibling;
    }
    // key相同返回fiber節(jié)點(diǎn),key不同返回null
    // 如果type相同復(fù)用節(jié)點(diǎn),不同返回新節(jié)點(diǎn)
    const newFiber = updateSlot(
      returnFiber,
      oldFiber,
      newChildren[newIdx],
      lanes,
    );
    // newFiber為null表示key不同,跳出循環(huán)
    if (newFiber === null) {
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }
    // newFiber.alternate為null就是新節(jié)點(diǎn),說(shuō)明type不同創(chuàng)建了新fiber節(jié)點(diǎn)
    if (oldFiber && newFiber.alternate === null) {
      // 需要把oldFiber標(biāo)記刪除
      deleteChild(returnFiber, oldFiber);
    }
    // 放置節(jié)點(diǎn),更新lastPlacedIndex
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    // 組成新fiber節(jié)點(diǎn)鏈
    if (previousNewFiber === null) {
      resultingFirstChild = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }
 
  /*
  第一輪遍歷完后新節(jié)點(diǎn)數(shù)量少于舊節(jié)點(diǎn)數(shù)量
  newChildren已經(jīng)遍歷完,刪除掉剩下的fiber節(jié)點(diǎn),可能情況如下 ??
  以前
  <li key="0"/>
  <li key="1"/>
  <li key="2"/>
  新的
  <li key="0"/>
  <li key="1"/>
  就會(huì)把<li key="2"/>刪除
   */
  if (newIdx === newChildren.length) {
    deleteRemainingChildren(returnFiber, oldFiber);
    return resultingFirstChild;
  }
 
  /*
  第一輪遍歷完新節(jié)點(diǎn)數(shù)量大于舊節(jié)點(diǎn)數(shù)量
  oldFiber已經(jīng)遍歷完,可能情況如下 ??
  以前
  <li key="0"/>
  <li key="1"/>
  新的
  <li key="0"/>
  <li key="1"/>
  <li key="2"/>
  就會(huì)添加新的<li key="2"/>,這一段是新節(jié)點(diǎn)的插入邏輯
   */
  if (oldFiber === null) {
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
      if (newFiber === null) {
        continue;
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      // 組成新fiber節(jié)點(diǎn)鏈
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    return resultingFirstChild;
  }
    
  // ---------------------------------------------------------------------
 
  // 用剩余的oldFiber創(chuàng)建一個(gè)key->fiber節(jié)點(diǎn)的Map,方便用key來(lái)獲取對(duì)應(yīng)的舊fiber節(jié)點(diǎn)
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
  
  // 第二輪遍歷,繼續(xù)遍歷剩余的節(jié)點(diǎn),這些節(jié)點(diǎn)可能是需要移動(dòng)或者刪除的
  for (; newIdx < newChildren.length; newIdx++) {
    // 從map中獲取對(duì)應(yīng)對(duì)應(yīng)key的舊節(jié)點(diǎn),返回更新后的新節(jié)點(diǎn)
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx],
      lanes,
    );
    if (newFiber !== null) {
      // 復(fù)用的新節(jié)點(diǎn),從map里刪除老的節(jié)點(diǎn),對(duì)應(yīng)的情況可能是位置的改變
      if (newFiber.alternate !== null) {
        // 復(fù)用的節(jié)點(diǎn)要移除map,因?yàn)閙ap里剩余的節(jié)點(diǎn)都會(huì)被標(biāo)記Deletion刪除
        existingChildren.delete(
          newFiber.key === null ? newIdx : newFiber.key,
        );
      }
      // 放置節(jié)點(diǎn)同時(shí)節(jié)點(diǎn)判斷是否移動(dòng)
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
  }
 
  // 刪除剩下的無(wú)用節(jié)點(diǎn)
  existingChildren.forEach(child => deleteChild(returnFiber, child));
 
  return resultingFirstChild;
}

第一輪遍歷比較好理解,這里再細(xì)分析一下第二輪遍歷,因?yàn)榈诙啎?huì)出現(xiàn)復(fù)用是否需要移動(dòng)的問(wèn)題。

第二輪遍歷首先遍歷剩余的oldFiber,組成一個(gè)key -> 舊fiber節(jié)點(diǎn)的Map,這用可以通過(guò)key來(lái)快速的獲取舊節(jié)點(diǎn)。

接下來(lái)的遍歷依然是使用的新節(jié)點(diǎn)為遍歷對(duì)象,每次遍歷使用新節(jié)點(diǎn)的key從Map中取出舊節(jié)點(diǎn)來(lái)對(duì)比是否能復(fù)用,對(duì)應(yīng)的函數(shù)為updateFromMap

如果節(jié)點(diǎn)存在alternate屬性,則是復(fù)用的節(jié)點(diǎn),這時(shí)候需要將它從existingChildren里移除,后續(xù)會(huì)把第二輪遍歷完后依然存在在existingChildren里的節(jié)點(diǎn)標(biāo)記為刪除。

如何判斷節(jié)點(diǎn)移動(dòng)了?

這里存在一個(gè)變量lastPlacedIndex用來(lái)判斷節(jié)點(diǎn)是否移動(dòng),每次將節(jié)點(diǎn)添加到新的Fiber鏈表中,都會(huì)更新這個(gè)值。

當(dāng)復(fù)用的節(jié)點(diǎn)oldIndex小于lastPlacedIndex時(shí),則為移動(dòng),如果不需要移動(dòng),則會(huì)將lastPlacedIndex更新為較大的oldIndex,下一個(gè)節(jié)點(diǎn)會(huì)以新值判斷,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function placeChild(
  newFiber: Fiber,
  lastPlacedIndex: number,
  newIndex: number,
): number {
  newFiber.index = newIndex;
  const current = newFiber.alternate;
  if (current !== null) {
    const oldIndex = current.index;
    if (oldIndex < lastPlacedIndex) {
            // 節(jié)點(diǎn)移動(dòng)
      newFiber.flags = Placement;
      return lastPlacedIndex;
    } else {
      // 節(jié)點(diǎn)位置無(wú)變化
      return oldIndex;
    }
  } else {
    // 插入的新節(jié)點(diǎn)
    newFiber.flags = Placement;
    return lastPlacedIndex;
  }
}

舉個(gè)例子:

?
1
2
3
4
// 舊
abcd
// 新
acbd

abcd均為key值。

第一輪遍歷后剩下的需要對(duì)比節(jié)點(diǎn):

?
1
2
3
4
// 舊
bcd
// 新
cbd

a節(jié)點(diǎn)在第一輪已經(jīng)復(fù)用,并且調(diào)用過(guò)placeChild,這時(shí)lastPlacedIndex值為0。

進(jìn)入第二輪遍歷,依然是以新節(jié)點(diǎn)為遍歷對(duì)象。

?
1
2
3
c => 在舊節(jié)點(diǎn)中存在,可復(fù)用,它的index在舊節(jié)點(diǎn)中為2,2 > lastPlacedIndex(0),不需要移動(dòng),將lastPlacedIndex賦值為2。
b => 在舊節(jié)點(diǎn)中存在,可復(fù)用,它的index在舊節(jié)點(diǎn)中為1,1 < lastPlacedIndex(2),需要移動(dòng),標(biāo)記Placement。
d => 在舊節(jié)點(diǎn)中存在,可復(fù)用,它的index在舊節(jié)點(diǎn)中為3,3 > lastPlacedIndex(2),不需要移動(dòng)。

由這個(gè)例子可以看出,React中將右側(cè)不需要移動(dòng)的節(jié)點(diǎn)作為參照,將需要移動(dòng)的節(jié)點(diǎn)都是統(tǒng)一從左向右移動(dòng)的。

在后續(xù)Layout階段會(huì)將這里標(biāo)記了Placement的節(jié)點(diǎn)做insertBefore操作。

總結(jié)

React中的Diff算法核心代碼不算很長(zhǎng),但是卻引入key巧妙的將復(fù)雜度由O(n3 )變?yōu)榱薕(n)。

碼農(nóng)內(nèi)卷太嚴(yán)重,所以不得不學(xué)習(xí)源碼了。

以上就是react diff算法源碼解析的詳細(xì)內(nèi)容,更多關(guān)于react diff算法的資料請(qǐng)關(guān)注服務(wù)器之家其它相關(guān)文章!

原文鏈接:https://juejin.cn/post/6949092569275957256

延伸 · 閱讀

精彩推薦
  • ReactReact State狀態(tài)與生命周期的實(shí)現(xiàn)方法

    React State狀態(tài)與生命周期的實(shí)現(xiàn)方法

    這篇文章主要介紹了React State狀態(tài)與生命周期的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考...

    一枚小棋子10862022-02-20
  • React使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過(guò)程詳解

    使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過(guò)程詳解

    這篇文章主要介紹了使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過(guò)程詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒...

    Windy Z11082022-02-23
  • Reactreact中常見(jiàn)hook的使用方式

    react中常見(jiàn)hook的使用方式

    這篇文章主要介紹了react中常見(jiàn)hook的使用方式與區(qū)別,幫助大家更好的理解和學(xué)習(xí)使用react,感興趣的朋友可以了解下...

    一顆冰淇淋8792022-02-25
  • React詳解react應(yīng)用中的DOM DIFF算法

    詳解react應(yīng)用中的DOM DIFF算法

    這篇文章主要介紹了react應(yīng)用中的DOM DIFF算法,幫助大家更好的理解和學(xué)習(xí)使用react,感興趣的朋友可以了解下...

    time_w6212022-02-25
  • React淺談react路由傳參的幾種方式

    淺談react路由傳參的幾種方式

    這篇文章主要介紹了淺談react路由傳參的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下...

    glorydx4582022-02-20
  • React懶惰開(kāi)發(fā)者需要知道 React Hack

    懶惰開(kāi)發(fā)者需要知道 React Hack

    本篇從八個(gè)方面來(lái)介紹關(guān)于React Hack的一些用法,懶惰開(kāi)發(fā)者的福音,快在你的代碼中試試這些小hack吧!...

    JavaScript之禪6622021-12-24
  • ReactReact實(shí)現(xiàn)todolist功能

    React實(shí)現(xiàn)todolist功能

    這篇文章主要介紹了React實(shí)現(xiàn)todolist功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下...

    一杯清泉7082021-12-21
  • React基于 Vite 的組件文檔編寫(xiě)神器,又快又省心

    基于 Vite 的組件文檔編寫(xiě)神器,又快又省心

    現(xiàn)在 Vite 的生態(tài)逐漸完善,今天給大家介紹一款 React 的組件/應(yīng)用文檔編寫(xiě)神器:vite-plugin-react-pages....

    前端星辰5072022-01-04
主站蜘蛛池模板: 一本色道久久综合亚洲精品图片 | 一级做a爱视频 | 被啪羞羞视频在线观看 | 黄色影院在线观看视频 | 精品国产91久久久 | 91福利社在线 | 黄色毛片视频在线观看 | 性看小视频 | 综合在线视频 | 国产欧美日韩视频在线观看 | 日韩欧美视频一区二区三区 | 黄色片网站在线播放 | 最新午夜综合福利视频 | 国产九色视频在线观看 | 久久久久亚洲美女啪啪 | 成人污在线 | 久在线播放 | 欧美三级欧美成人高清www | 久草在线网址 | 永久免费av片在线观看全网站 | 中文日韩在线视频 | 一级做人爱c黑人影片 | av免费片 | 亚洲小视频在线 | 日韩精品久久久久久久电影99爱 | 2021狠狠操 | 九九热精 | 性爱视频免费 | 伊人久久国产精品 | 久久欧美亚洲另类专区91大神 | 久久凹凸 | 国产视频在线免费观看 | 久久久久久久久久久国产精品 | 黑色丝袜美美女被躁视频 | 欧美性生视频 | 精品国产一区二区三区四 | 欧美日性 | 欧美1区2区在线观看 | 蜜桃网站免费 | 成人免费网站在线观看视频 | 羞羞视频免费网站含羞草 |