最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React?diff算法超詳細(xì)講解

 更新時(shí)間:2022年11月05日 09:37:59   作者:goClient1992  
渲染真實(shí)DOM的開(kāi)銷很大,有時(shí)候我們修改了某個(gè)數(shù)據(jù),直接渲染到真實(shí)dom上會(huì)引起整個(gè)dom樹(shù)的重繪和重排。我們希望只更新我們修改的那一小塊dom,而不是整個(gè)dom,diff算法就幫我們實(shí)現(xiàn)了這點(diǎn)。diff算法的本質(zhì)就是:找出兩個(gè)對(duì)象之間的差異,目的是盡可能做到節(jié)點(diǎn)復(fù)用

上一章中 react 的 render 階段,其中 begin 時(shí)會(huì)調(diào)用 reconcileChildren函數(shù), reconcileChildren 中做的事情就是 react 知名的 diff 過(guò)程,本章會(huì)對(duì) diff 算法進(jìn)行講解。

diff 算法介紹

react 的每次更新,都會(huì)將新的 ReactElement 內(nèi)容與舊的 fiber 樹(shù)作對(duì)比,比較出它們的差異后,構(gòu)建新的 fiber 樹(shù),將差異點(diǎn)放入更新隊(duì)列之中,從而對(duì)真實(shí) dom 進(jìn)行 render。簡(jiǎn)單來(lái)說(shuō)就是如何通過(guò)最小代價(jià)將舊的 fiber 樹(shù)轉(zhuǎn)換為新的 fiber 樹(shù)。

經(jīng)典的 diff 算法 中,將一棵樹(shù)轉(zhuǎn)為另一棵樹(shù)的最低時(shí)間復(fù)雜度為 O(n^3),其中 n 為樹(shù)種節(jié)點(diǎn)的個(gè)數(shù)。假如采用這種 diff 算法,一個(gè)應(yīng)用有 1000 個(gè)節(jié)點(diǎn)的情況下,需要比較 十億 次才能將 dom 樹(shù)更新完成,顯然這個(gè)性能是無(wú)法讓人接受的。

因此,想要將 diff 應(yīng)用于 virtual dom 中,必須實(shí)現(xiàn)一種高效的 diff 算法。React 便通過(guò)制定了一套大膽的策略,實(shí)現(xiàn)了 O(n) 的時(shí)間復(fù)雜度更新 virtual dom。

diff 策略

react 將 diff 算法優(yōu)化到 O(n) 的時(shí)間復(fù)雜度,基于了以下三個(gè)前提策略:

  • 只對(duì)同級(jí)元素進(jìn)行比較。Web UI 中 DOM 節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作特別少,可以忽略不計(jì),如果出現(xiàn)跨層級(jí)的 dom 節(jié)點(diǎn)更新,則不進(jìn)行復(fù)用。
  • 兩個(gè)不同類型的組件會(huì)產(chǎn)生兩棵不同的樹(shù)形結(jié)構(gòu)。
  • 對(duì)同一層級(jí)的子節(jié)點(diǎn),開(kāi)發(fā)者可以通過(guò) key 來(lái)確定哪些子元素可以在不同渲染中保持穩(wěn)定。

上面的三種 diff 策略,分別對(duì)應(yīng)著 tree diff、component diff 和 element diff。

tree diff

根據(jù)策略一,react 會(huì)對(duì) fiber 樹(shù)進(jìn)行分層比較,只比較同級(jí)元素。這里的同級(jí)指的是同一個(gè)父節(jié)點(diǎn)下的子節(jié)點(diǎn)(往上的祖先節(jié)點(diǎn)也都是同一個(gè)),而不是樹(shù)的深度相同。

如上圖所示,react 的 tree diff 是采用深度優(yōu)先遍歷,所以要比較的元素向上的祖先元素都會(huì)一致,即圖中會(huì)對(duì)相同顏色的方框內(nèi)圈出的元素進(jìn)行比較,例如左邊樹(shù)的 A 節(jié)點(diǎn)下的子節(jié)點(diǎn) C、D 會(huì)與右邊樹(shù) A 節(jié)點(diǎn)下的 C、D、E進(jìn)行比較。

當(dāng)元素出現(xiàn)跨層級(jí)的移動(dòng)時(shí),例如下圖:

A 子樹(shù)從 root 節(jié)點(diǎn)下到了 B 節(jié)點(diǎn)下,在 react diff 過(guò)程中并不會(huì)直接將 A 子樹(shù)移動(dòng)到 B 子樹(shù)下,而是進(jìn)行如下操作:

  • 在 root 節(jié)點(diǎn)下刪除 A 節(jié)點(diǎn)
  • 在 B 節(jié)點(diǎn)下創(chuàng)建 A 子節(jié)點(diǎn)
  • 在新創(chuàng)建的 A 子節(jié)點(diǎn)下創(chuàng)建 C、D 節(jié)點(diǎn)

component diff

對(duì)于組件之間的比較,只要它們的類型不同,就判斷為它們是兩棵不同的樹(shù)形結(jié)構(gòu),直接會(huì)將它們給替換掉。

例如下面的兩棵樹(shù),左邊樹(shù) B 節(jié)點(diǎn)和右邊樹(shù) K 節(jié)點(diǎn)除了類型不同(比如 B 為 div 類型,K 為 p 類型),內(nèi)容完全一致,但 react 依然后直接替換掉整個(gè)節(jié)點(diǎn)。實(shí)際經(jīng)過(guò)的變換是:

  • 在 root 節(jié)點(diǎn)下創(chuàng)建 K 節(jié)點(diǎn)
  • 在 K 節(jié)點(diǎn)下創(chuàng)建 E、F 節(jié)點(diǎn)
  • 在 F 節(jié)點(diǎn)下創(chuàng)建 G、H 節(jié)點(diǎn)
  • 在 root 節(jié)點(diǎn)下刪除 B 子節(jié)點(diǎn)

雖然如果在本例中改變類型復(fù)用子元素性能會(huì)更高一點(diǎn),但是在時(shí)機(jī)應(yīng)用開(kāi)發(fā)中類型不一致子內(nèi)容完全一致的情況極少,對(duì)這種情況過(guò)多判斷反而會(huì)增加時(shí)機(jī)復(fù)雜度,降低平均性能。

element diff

react 對(duì)于同層級(jí)的元素進(jìn)行比較時(shí),會(huì)通過(guò) key 對(duì)元素進(jìn)行比較以識(shí)別哪些元素可以穩(wěn)定的渲染。同級(jí)元素的比較存在插入、刪除和移動(dòng)三種操作。

如下圖左邊的樹(shù)想要轉(zhuǎn)變?yōu)橛疫叺臉?shù):

實(shí)際經(jīng)過(guò)的變換如下:

  • 將 root 節(jié)點(diǎn)下 A 子節(jié)點(diǎn)移動(dòng)至 B 子節(jié)點(diǎn)之后
  • 在 root 節(jié)點(diǎn)下新增 E 子節(jié)點(diǎn)
  • 將 root 節(jié)點(diǎn)下 C 子節(jié)點(diǎn)刪除

結(jié)合源碼看 diff

整體流程

diff 算法從 reconcileChildren 函數(shù)開(kāi)始,根據(jù)當(dāng)前 fiber 是否存在,決定是直接渲染新的 ReactElement 內(nèi)容還是與當(dāng)前 fiber 去進(jìn)行 Diff,相關(guān)參考視頻講解:傳送門(mén)

export function reconcileChildren(
  current: Fiber | null, // 當(dāng)前 fiber 節(jié)點(diǎn)
  workInProgress: Fiber, // 父 fiber
  nextChildren: any, // 新生成的 ReactElement 內(nèi)容
  renderLanes: Lanes, // 渲染的優(yōu)先級(jí)
) {
  if (current === null) {
    // 如果當(dāng)前 fiber 節(jié)點(diǎn)為空,則直接將新的 ReactElement 內(nèi)容生成新的 fiber
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // 當(dāng)前 fiber 節(jié)點(diǎn)不為空,則與新生成的 ReactElement 內(nèi)容進(jìn)行 diff
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

因?yàn)槲覀冎饕且獙W(xué)習(xí) diff 算法,所以我們暫時(shí)先不關(guān)心 mountChildFibers 函數(shù),主要關(guān)注 reconcileChildFibers ,我們來(lái)看一下它的源碼:

function reconcileChildFibers(
  returnFiber: Fiber, // 父 Fiber
  currentFirstChild: Fiber | null, // 父 fiber 下要對(duì)比的第一個(gè)子 fiber
  newChild: any, // 更新后的 React.Element 內(nèi)容
  lanes: Lanes, // 更新的優(yōu)先級(jí)
): Fiber | null {
  // 對(duì)新創(chuàng)建的 ReactElement 最外層是 fragment 類型單獨(dú)處理,比較其 children
  const isUnkeyedTopLevelFragment =
    typeof newChild === 'object' &&
    newChild !== null &&
    newChild.type === REACT_FRAGMENT_TYPE &&
    newChild.key === null;
  if (isUnkeyedTopLevelFragment) {
    newChild = newChild.props.children;
  }
  // 對(duì)更新后的 React.Element 是單節(jié)點(diǎn)的處理
  if (typeof newChild === 'object' && newChild !== null) {
    switch (newChild.$$typeof) {
      // 常規(guī) react 元素
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(
          reconcileSingleElement(
            returnFiber,
            currentFirstChild,
            newChild,
            lanes,
          ),
        );
      // react.portal 類型
      case REACT_PORTAL_TYPE:
        return placeSingleChild(
          reconcileSinglePortal(
            returnFiber,
            currentFirstChild,
            newChild,
            lanes,
          ),
        );
      // react.lazy 類型
      case REACT_LAZY_TYPE:
        if (enableLazyElements) {
          const payload = newChild._payload;
          const init = newChild._init;
          return reconcileChildFibers(
            returnFiber,
            currentFirstChild,
            init(payload),
            lanes,
          );
        }
    }
    // 更新后的 React.Element 是多節(jié)點(diǎn)的處理
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }
    // 迭代器函數(shù)的單獨(dú)處理
    if (getIteratorFn(newChild)) {
      return reconcileChildrenIterator(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }
    throwOnInvalidObjectType(returnFiber, newChild);
  }
  // 純文本節(jié)點(diǎn)的類型處理
  if (typeof newChild === 'string' || typeof newChild === 'number') {
    return placeSingleChild(
      reconcileSingleTextNode(
        returnFiber,
        currentFirstChild,
        '' + newChild,
        lanes,
      ),
    );
  }
  if (__DEV__) {
    if (typeof newChild === 'function') {
      warnOnFunctionType(returnFiber);
    }
  }
  // 不符合以上情況都視為 empty,直接從父節(jié)點(diǎn)刪除所有舊的子 Fiber
  return deleteRemainingChildren(returnFiber, currentFirstChild);
}

入口函數(shù)中,接收 returnFiber、currentFirstChildnewChild、lanes 四個(gè)參數(shù),其中,根據(jù) newChid 的類型,我們主要關(guān)注幾個(gè)比較常見(jiàn)的類型的 diff,單 React 元素的 diff、純文本類型的 diff 和 數(shù)組類型的 diff。

所以根據(jù) ReactElement 類型走的不同流程如下:

新內(nèi)容為 REACT_ELEMENT_TYPE

當(dāng)新創(chuàng)建的節(jié)點(diǎn) type 為 object 時(shí),我們看一下其為 REACT_ELEMENT_TYPE 類型的 diff,即 placeSingleChild(reconcileSingleElement(...)) 函數(shù)。

先看一下 reconcileSingleElement 函數(shù)的源碼:

function reconcileSingleElement(
  returnFiber: Fiber, // 父 fiber
  currentFirstChild: Fiber | null, // 父 fiber 下第一個(gè)開(kāi)始對(duì)比的舊的子  fiber
  element: ReactElement, // 當(dāng)前的 ReactElement內(nèi)容
  lanes: Lanes, // 更新的優(yōu)先級(jí)
): Fiber {
  const key = element.key;
  let child = currentFirstChild;
  // 處理舊的 fiber 由多個(gè)節(jié)點(diǎn)變成新的 fiber 一個(gè)節(jié)點(diǎn)的情況
  // 循環(huán)遍歷父 fiber 下的舊的子 fiber,直至遍歷完或者找到 key 和 type 都與新節(jié)點(diǎn)相同的情況
  while (child !== null) {
    if (child.key === key) {
      const elementType = element.type;
      if (elementType === REACT_FRAGMENT_TYPE) {
        if (child.tag === Fragment) {
          // 如果新的 ReactElement 和舊 Fiber 都是 fragment 類型且 key 相等
          // 對(duì)舊 fiber 后面的所有兄弟節(jié)點(diǎn)添加 Deletion 副作用標(biāo)記,用于 dom 更新時(shí)刪除
          deleteRemainingChildren(returnFiber, child.sibling);
          // 通過(guò) useFiber, 基于舊的 fiber 和新的 props.children,克隆生成一個(gè)新的 fiber,新 fiber 的 index 為 0,sibling 為 null
          // 這便是所謂的 fiber 復(fù)用
          const existing = useFiber(child, element.props.children);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
      } else {
        if (
          // 如果新的 ReactElement 和舊 Fiber 的 key 和 type 都相等
          child.elementType === elementType ||
          (__DEV__
            ? isCompatibleFamilyForHotReloading(child, element)
            : false) ||
          (enableLazyElements &&
            typeof elementType === 'object' &&
            elementType !== null &&
            elementType.$$typeof === REACT_LAZY_TYPE &&
            resolveLazy(elementType) === child.type)
        ) {
          // 對(duì)舊 fiber 后面的所有兄弟節(jié)點(diǎn)添加 Deletion 副作用標(biāo)記,用于 dom 更新時(shí)刪除
          deleteRemainingChildren(returnFiber, child.sibling);
          // 通過(guò) useFiber 復(fù)用新節(jié)點(diǎn)并返回
          const existing = useFiber(child, element.props);
          existing.ref = coerceRef(returnFiber, child, element);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
      }
      // 若 key 相同但是 type 不同說(shuō)明不匹配,移除舊 fiber 及其后面的兄弟 fiber
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      // 若 key 不同,對(duì)當(dāng)前的舊 fiber 添加 Deletion 副作用標(biāo)記,繼續(xù)對(duì)其兄弟節(jié)點(diǎn)遍歷
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }
  // 都遍歷完之后說(shuō)明沒(méi)有匹配到 key 和 type 都相同的 fiber
  if (element.type === REACT_FRAGMENT_TYPE) {
    // 如果新節(jié)點(diǎn)是 fragment 類型,createFiberFromFragment 創(chuàng)建新的 fragment 類型 fiber并返回
    const created = createFiberFromFragment(
      element.props.children,
      returnFiber.mode,
      lanes,
      element.key,
    );
    created.return = returnFiber;
    return created;
  } else {
    // createFiberFromElement 創(chuàng)建 fiber 并返回
    const created = createFiberFromElement(element, returnFiber.mode, lanes);
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}

根據(jù)源碼我們可以得知,reconcileSingleElement 函數(shù)中,會(huì)遍歷父 fiber 下所有的舊的子 fiber,尋找與新生成的 ReactElement 內(nèi)容的 key 和 type 都相同的子 fiber。每次遍歷對(duì)比的過(guò)程中:

  • 若當(dāng)前舊的子 fiber 與新內(nèi)容 key 或 type 不一致,對(duì)當(dāng)前舊的子 fiber 添加 Deletion 副作用標(biāo)記(用于 dom 更新時(shí)刪除),繼續(xù)對(duì)比下一個(gè)舊子 fiber
  • 若當(dāng)前舊的子 fiber 與新內(nèi)容 key 或 type 一致,則判斷為可復(fù)用,通過(guò) deleteRemainingChildren 對(duì)該子 fiber 后面所有的兄弟 fiber 添加 Deletion 副作用標(biāo)記,然后通過(guò) useFiber 基于該子 fiber 和新內(nèi)容的 props 生成新的 fiber 進(jìn)行復(fù)用,結(jié)束遍歷。

若都遍歷完沒(méi)找到與新內(nèi)容 key 或 type 子 fiber,此時(shí)父 fiber 下的所有舊的子 fiber 都已經(jīng)添加了 Deletion 副作用標(biāo)記,通過(guò) createFiberFromElement 基于新內(nèi)容創(chuàng)建新的 fiber 并將其 return指向父 fiber。

再來(lái)看 placeSingleChild 的源碼:

function placeSingleChild(newFiber: Fiber): Fiber {
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags |= Placement;
  }
  return newFiber;
}

placeSingleChild 中做的事情更為簡(jiǎn)單,就是將 reconcileSingleElement 中生成的新 fiber 打上 Placement 的標(biāo)記,表示 dom 更新渲染時(shí)要進(jìn)行插入。

新內(nèi)容為純文本類型

當(dāng)新創(chuàng)建節(jié)點(diǎn)的 typeof 為 string 或者 number 時(shí),表示是純文本節(jié)點(diǎn),使用 placeSingleChild(reconcileSingleTextNode(...)) 函數(shù)進(jìn)行 diff。

placeSingleChild 前面說(shuō)過(guò)了,我們主要看 reconcileSingleTextNode 的源碼:

function reconcileSingleTextNode(
  returnFiber: Fiber,  currentFirstChild: Fiber | null,  textContent: string,  lanes: Lanes,
): Fiber {
  if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
    // deleteRemainingChildren 對(duì)舊 fiber 后面的所有兄弟節(jié)點(diǎn)添加 Deletion 副作用標(biāo)記,用于 dom 更新時(shí)刪除
    // useFiber 傳入 textContext 復(fù)用當(dāng)前 fiber
    deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
    const existing = useFiber(currentFirstChild, textContent);
    existing.return = returnFiber;
    return existing;
  }
  // 若未匹配到,createFiberFromText 創(chuàng)建新的 fiber
  deleteRemainingChildren(returnFiber, currentFirstChild);
  const created = createFiberFromText(textContent, returnFiber.mode, lanes);
  created.return = returnFiber;
  return created;
}

新內(nèi)容為純文本時(shí) diff 比較簡(jiǎn)單,只需要判斷當(dāng)前父 fiber 的第一個(gè)舊子 fiber 類型:

  • 當(dāng)前 fiber 也為文本類型的節(jié)點(diǎn)時(shí),deleteRemainingChildren 對(duì)第一個(gè)舊子 fiber 的所有兄弟 fiber 添加 Deletion 副作用標(biāo)記,然后通過(guò) useFiber 基于當(dāng)前 fiber 和 textContent 創(chuàng)建新的 fiber 復(fù)用,將其 return 指向父 fiber
  • 否則通過(guò) deleteRemainingChildren 對(duì)所有舊的子 fiber 添加 Deletion 副作用標(biāo)記,然后 createFiberFromText 創(chuàng)建新的文本類型 fiber 節(jié)點(diǎn),將其 return 指向父 fiber

所以對(duì)文本類型 diff 的流程如下:

新內(nèi)容為數(shù)組類型

上面所說(shuō)的兩種情況,都是一個(gè)或多個(gè)子 fiebr 變成單個(gè) fiber。新內(nèi)容為數(shù)組類型時(shí),意味著要將一個(gè)或多個(gè)子 fiber 替換為多個(gè) fiber,內(nèi)容相對(duì)復(fù)雜,我們看一下 reconcileChildrenArray 的源碼:

function reconcileChildrenArray(
  returnFiber: Fiber,  currentFirstChild: Fiber | null,  newChildren: Array<*>,  lanes: Lanes,
): Fiber | null {
  // 開(kāi)發(fā)環(huán)境下會(huì)校驗(yàn) key 是否存在且合法,否則會(huì)報(bào) warning
  if (__DEV__) {
    let knownKeys = null;
    for (let i = 0; i < newChildren.length; i++) {
      const child = newChildren[i];
      knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);
    }
  }
  let resultingFirstChild: Fiber | null = null; // 最終要返回的第一個(gè)子 fiber
  let previousNewFiber: Fiber | null = null;
  let oldFiber = currentFirstChild;
  let lastPlacedIndex = 0;
  let newIdx = 0;
  let nextOldFiber = null;
  // 因?yàn)樵趯?shí)際的應(yīng)用開(kāi)發(fā)中,react 發(fā)現(xiàn)更新的情況遠(yuǎn)大于新增和刪除的情況,所以這里優(yōu)先處理更新
  // 根據(jù) oldFiber 的 index 和 newChildren 的下標(biāo),找到要對(duì)比更新的 oldFiber
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    if (oldFiber.index > newIdx) {
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      nextOldFiber = oldFiber.sibling;
    }
    // 通過(guò) updateSlot 來(lái) diff oldFiber 和新的 child,生成新的 Fiber
    // updateSlot 與上面兩種類型的 diff 類似,如果 oldFiber 可復(fù)用,則根據(jù) oldFiber 和 child 的 props 生成新的 fiber;否則返回 null
    const newFiber = updateSlot(
      returnFiber,
      oldFiber,
      newChildren[newIdx],
      lanes,
    );
    // newFiber 為 null 說(shuō)明不可復(fù)用,退出第一輪的循環(huán)
    if (newFiber === null) {
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }
    if (shouldTrackSideEffects) {
      if (oldFiber && newFiber.alternate === null) {
        deleteChild(returnFiber, oldFiber);
      }
    }
    // 記錄復(fù)用的 oldFiber 的 index,同時(shí)給新 fiber 打上 Placement 副作用標(biāo)簽
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    if (previousNewFiber === null) {
      // 如果上一個(gè) newFiber 為 null,說(shuō)明這是第一個(gè)生成的 newFiber,設(shè)置為 resultingFirstChild
      resultingFirstChild = newFiber;
    } else {
      // 否則構(gòu)建鏈?zhǔn)疥P(guān)系
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }
  if (newIdx === newChildren.length) {
    // newChildren遍歷完了,說(shuō)明剩下的 oldFiber 都是待刪除的 Fiber
    // 對(duì)剩下 oldFiber 標(biāo)記 Deletion
    deleteRemainingChildren(returnFiber, oldFiber);
    return resultingFirstChild;
  }
  if (oldFiber === null) {
    // olderFiber 遍歷完了
    // newChildren 剩下的節(jié)點(diǎn)都是需要新增的節(jié)點(diǎn)
    for (; newIdx < newChildren.length; newIdx++) {
      // 遍歷剩下的 child,通過(guò) createChild 創(chuàng)建新的 fiber
      const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
      if (newFiber === null) {
        continue;
      }
      // 處理dom移動(dòng),// 記錄 index,同時(shí)給新 fiber 打上 Placement 副作用標(biāo)簽
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      // 將新創(chuàng)建 fiber 加入到 fiber 鏈表樹(shù)中
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    return resultingFirstChild;
  }
  // oldFiber 和 newChildren 都未遍歷完
  // mapRemainingChildren 生成一個(gè)以 oldFiber 的 key 為 key, oldFiber 為 value 的 map
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
  // 對(duì)剩下的 newChildren 進(jìn)行遍歷
  for (; newIdx < newChildren.length; newIdx++) {
    // 找到 mapRemainingChildren 中 key 相等的 fiber, 創(chuàng)建新 fiber 復(fù)用
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx],
      lanes,
    );
    if (newFiber !== null) {
      if (shouldTrackSideEffects) {
        if (newFiber.alternate !== null) {
          // 刪除當(dāng)前找到的 fiber
          existingChildren.delete(
            newFiber.key === null ? newIdx : newFiber.key,
          );
        }
      }
      // 處理dom移動(dòng),記錄 index,同時(shí)給新 fiber 打上 Placement 副作用標(biāo)簽
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      // 將新創(chuàng)建 fiber 加入到 fiber 鏈表樹(shù)中
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
  }
  if (shouldTrackSideEffects) {
    // 剩余的舊 fiber 的打上 Deletion 副作用標(biāo)簽
    existingChildren.forEach(child => deleteChild(returnFiber, child));
  }
  return resultingFirstChild;
}

從上述代碼我們可以得知,對(duì)于新增內(nèi)容為數(shù)組時(shí),react 會(huì)對(duì)舊 fiber 和 newChildren 進(jìn)行遍歷。

首先先對(duì) newChildren 進(jìn)行第一輪遍歷,將當(dāng)前的 oldFiber 與 當(dāng)前 newIdx 下標(biāo)的 newChild 通過(guò) updateSlot 進(jìn)行 diff,diff 的流程和上面單節(jié)點(diǎn)的 diff 類似,然后返回 diff 后的結(jié)果:

  • 如果 diff 后 oldFiber 和 newIdx 的 key 和 type 一致,說(shuō)明可復(fù)用。根據(jù) oldFiber 和 newChild 的 props 生成新的 fiber,通過(guò) placeChild 給新生成的 fiber 打上 Placement 副作用標(biāo)記,同時(shí)新 fiber 與之前遍歷生成的新 fiber 構(gòu)建鏈表樹(shù)關(guān)系。然后繼續(xù)執(zhí)行遍歷,對(duì)下一個(gè) oldFiber 和下一個(gè) newIdx 下標(biāo)的 newFiber 繼續(xù) diff
  • 如果 diff 后 oldFiber 和 newIdx 的 key 或 type 不一致,那么說(shuō)明不可復(fù)用,返回的結(jié)果為 null,第一輪遍歷結(jié)束

第一輪遍歷結(jié)束后,可能會(huì)執(zhí)行以下幾種情況:

  • 若 newChildren 遍歷完了,那剩下的 oldFiber 都是待刪除的,通過(guò) deleteRemainingChildren 對(duì)剩下的 oldFiber 打上 Deletion 副作用標(biāo)記
  • 若 oldFiber 遍歷完了,那剩下的 newChildren 都是需要新增的,遍歷剩下的 newChildren,通過(guò) createChild 創(chuàng)建新的 fiber,placeChild 給新生成的 fiber 打上 Placement 副作用標(biāo)記并添加到 fiber 鏈表樹(shù)中。
  • 若 oldFiber 和 newChildren 都未遍歷完,通過(guò) mapRemainingChildren 創(chuàng)建一個(gè)以剩下的 oldFiber 的 key 為 key,oldFiber 為 value 的 map。然后對(duì)剩下的 newChildren 進(jìn)行遍歷,通過(guò) updateFromMap 在 map 中尋找具有相同 key 創(chuàng)建新的fiber(若找到則基于 oldFiber 和 newChild 的 props創(chuàng)建,否則直接基于 newChild 創(chuàng)建),則從 map 中刪除當(dāng)前的 key,然后placeChild 給新生成的 fiber 打上 Placement 副作用標(biāo)記并添加到 fiber 鏈表樹(shù)中。遍歷完之后則 existingChildren 還剩下 oldFiber 的話,則都是待刪除的 fiber,deleteChild 對(duì)其打上 Deletion 副作用標(biāo)記。

diff 后的渲染

diff 流程結(jié)束后,會(huì)形成新的 fiber 鏈表樹(shù),鏈表樹(shù)上的 fiber 通過(guò) flags 字段做了副作用標(biāo)記,主要有以下幾種:

  1. Deletion:會(huì)在渲染階段對(duì)對(duì)應(yīng)的 dom 做刪除操作
  2. Update:在 fiber.updateQueue 上保存了要更新的屬性,在渲染階段會(huì)對(duì) dom 做更新操作
  3. Placement:Placement 可能是插入也可能是移動(dòng),實(shí)際上兩種都是插入動(dòng)作。react 在更新時(shí)會(huì)優(yōu)先去尋找要插入的 fiber 的 sibling,如果找到了執(zhí)行 dom 的 insertBefore 方法,如果沒(méi)有找到就執(zhí)行 dom 的 appendChild 方法,從而實(shí)現(xiàn)了新節(jié)點(diǎn)插入位置的準(zhǔn)確性

completeUnitWork 階段結(jié)束后,react 會(huì)根據(jù) fiber 鏈表樹(shù)的 flags,構(gòu)建一個(gè) effectList 鏈表,里面記錄了哪些 fiber 需要進(jìn)行插入、刪除、更新操作,在后面的 commit 階段進(jìn)行真實(shí) dom 節(jié)點(diǎn)的更新,下一章將詳細(xì)講述 commit 階段。

到此這篇關(guān)于React diff算法超詳細(xì)講解的文章就介紹到這了,更多相關(guān)React diff算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React-native橋接Android原生開(kāi)發(fā)詳解

    React-native橋接Android原生開(kāi)發(fā)詳解

    本篇文章主要介紹了React-native橋接Android原生開(kāi)發(fā)詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • react-native動(dòng)態(tài)切換tab組件的方法

    react-native動(dòng)態(tài)切換tab組件的方法

    在APP中免不了要使用tab組件,有的是tab切換,也有的是tab分類切換.這篇文章主要介紹了react-native動(dòng)態(tài)切換tab組件的方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-07-07
  • 詳解React開(kāi)發(fā)必不可少的eslint配置

    詳解React開(kāi)發(fā)必不可少的eslint配置

    本篇文章主要介紹了詳解React開(kāi)發(fā)必不可少的eslint配置,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • 詳解如何在React單頁(yè)面應(yīng)用中捕獲錯(cuò)誤

    詳解如何在React單頁(yè)面應(yīng)用中捕獲錯(cuò)誤

    在當(dāng)前的Web開(kāi)發(fā)中,使用React構(gòu)建單頁(yè)面應(yīng)用(SPA)已經(jīng)成為一種常見(jiàn)的做法,然而,當(dāng)應(yīng)用程序遇到錯(cuò)誤時(shí),有可能會(huì)導(dǎo)致整個(gè)頁(yè)面崩潰,給用戶帶來(lái)不好的體驗(yàn),本文將介紹如何在React單頁(yè)面應(yīng)用中捕獲錯(cuò)誤,以防止整個(gè)頁(yè)面的崩潰,需要的朋友可以參考下
    2023-09-09
  • react中代碼塊輸出,代碼高亮顯示,帶行號(hào),能復(fù)制的問(wèn)題

    react中代碼塊輸出,代碼高亮顯示,帶行號(hào),能復(fù)制的問(wèn)題

    這篇文章主要介紹了react中代碼塊輸出,代碼高亮顯示,帶行號(hào),能復(fù)制的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • webpack手動(dòng)配置React開(kāi)發(fā)環(huán)境的步驟

    webpack手動(dòng)配置React開(kāi)發(fā)環(huán)境的步驟

    本篇文章主要介紹了webpack手動(dòng)配置React開(kāi)發(fā)環(huán)境的步驟,webpack手動(dòng)配置一個(gè)獨(dú)立的React開(kāi)發(fā)環(huán)境, 開(kāi)發(fā)環(huán)境完成后, 支持自動(dòng)構(gòu)建, 自動(dòng)刷新, sass語(yǔ)法 等功能...感興趣的小伙伴們可以參考一下
    2018-07-07
  • React中classnames庫(kù)使用示例

    React中classnames庫(kù)使用示例

    這篇文章主要為大家介紹了React中classnames庫(kù)使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • React如何使用錯(cuò)誤邊界(Error Boundaries)捕獲組件錯(cuò)誤

    React如何使用錯(cuò)誤邊界(Error Boundaries)捕獲組件錯(cuò)誤

    在 React 里,錯(cuò)誤邊界就像是一個(gè)“小衛(wèi)士”,專門(mén)負(fù)責(zé)在組件出現(xiàn)錯(cuò)誤時(shí)挺身而出,避免整個(gè)應(yīng)用因?yàn)橐粋€(gè)小錯(cuò)誤就崩潰掉,下面小編就來(lái)為大家介紹一下如何利用它捕獲組件錯(cuò)誤吧
    2025-03-03
  • webpack 2.x配置reactjs基本開(kāi)發(fā)環(huán)境詳解

    webpack 2.x配置reactjs基本開(kāi)發(fā)環(huán)境詳解

    本篇文章主要介紹了webpack 2.x配置reactjs基本開(kāi)發(fā)環(huán)境詳解,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-08-08
  • React 高階組件入門(mén)介紹

    React 高階組件入門(mén)介紹

    本篇文章主要介紹了React高階組件入門(mén)介紹,這篇文章中我們?cè)敿?xì)的介紹了什么是高階組件,如何使用高階組件,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01

最新評(píng)論

汽车| 枞阳县| 五原县| 平塘县| 东乡县| 昭觉县| 共和县| 板桥市| 宣化县| 民和| 南通市| 大冶市| 陆河县| 巨鹿县| 湖州市| 伊川县| 永丰县| 五河县| 建湖县| 龙岩市| 高平市| 珲春市| 太康县| 全椒县| 绥化市| 瑞金市| 新营市| 皋兰县| 保山市| 开远市| 西吉县| 黔东| 墨玉县| 江永县| 怀安县| 资中县| 安福县| 曲麻莱县| 定边县| 林州市| 兰州市|