自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React Diff 算法的源碼應(yīng)該怎么讀

開(kāi)發(fā) 前端
這篇文章主要是為了幫助大家梳理 diff 算法在源碼中的實(shí)現(xiàn)思路、位置,然后讓我們自己就有能力去總結(jié)出自己的獨(dú)特理解。

這兩個(gè)月時(shí)間密集型的輔導(dǎo)了我的幾個(gè)學(xué)生通過(guò)大廠面試,由于面試強(qiáng)度非常高,因此在準(zhǔn)備面試時(shí),就對(duì)原理這一塊的深度有非常大的要求,不能僅僅停留在八股文的層面去糊弄面試官,回答的東西要禁得起拷打。

于是我就專門又重新花了一點(diǎn)時(shí)間去讀了一下 React 19 的源碼。在讀的過(guò)程中又有了一些新的體會(huì)。

這篇文章準(zhǔn)備給大家分享一個(gè)面試中比較容易被深入探討到的點(diǎn):diff 算法。如果你的薪資訴求非常高,在 30 K 以上,那么對(duì)于這一塊的理解,就不是隨便在網(wǎng)上找一篇文章學(xué)一下背一下就能達(dá)到要求的了。就要求我們自己有能力去源碼中尋找答案。這篇文章主要是為了幫助大家梳理 diff 算法在源碼中的實(shí)現(xiàn)思路、位置,然后讓我們自己就有能力去總結(jié)出自己的獨(dú)特理解。

一、函數(shù)緩存優(yōu)化

在前端開(kāi)發(fā)中,有一種比較常用的優(yōu)化方式。當(dāng)我們執(zhí)行一個(gè)函數(shù)所需要消耗的時(shí)間偏長(zhǎng)時(shí),第二次執(zhí)行時(shí),我們可以讀取上一次的執(zhí)行結(jié)果,從而跳過(guò)運(yùn)算過(guò)程,直接輸出結(jié)果。

思路大概如下,首先我們定義一個(gè)用于緩存的對(duì)象。

const cache = {
  preA: null,
  preB: null,
  preResult: null
}

這里我們假設(shè) expensive(a, b) 需要花費(fèi)很長(zhǎng)時(shí)間,我們要盡量避免重復(fù)運(yùn)算它。

function cul(a, b) {
  return expensive(a, b)
}

于是,我們?cè)诘诙螆?zhí)行 cal 時(shí),就需要提前判斷入?yún)ⅰH绻覀儼l(fā)現(xiàn),兩次的執(zhí)行入?yún)⑹且粯拥模敲次覀兙涂梢圆槐刂匦逻\(yùn)算,而是直接返回上一次的運(yùn)算結(jié)果

代碼調(diào)整如下:

function cul(a, b) {
  // 對(duì)比之后選擇復(fù)用緩存的結(jié)果
  if (cache.preA === a && cache.preB === b) {
    return cache.preResult
  }
  // 緩存參數(shù)與結(jié)果
  cache.preA = a
  cache.preB = b
  const result = expensive(a, b)
  cache.preResult = result
  return result
}

那么,當(dāng)我們多次執(zhí)行如下代碼的時(shí)候,耗時(shí)運(yùn)算 expensive() 僅會(huì)執(zhí)行一次。

cul(1000, 999)
cul(1000, 999)
cul(1000, 999)
cul(1000, 999)
cul(1000, 999)

React 的底層更新機(jī)制與這個(gè)簡(jiǎn)化版的優(yōu)化策略一模一樣。只不過(guò) React 遇到的需要緩存的內(nèi)容稍微復(fù)雜一點(diǎn),他的數(shù)據(jù)結(jié)果從一個(gè)普通的 cache 對(duì)象,變成了一個(gè)完整的 Fiber 鏈表。

二、Fiber 鏈表結(jié)構(gòu)

我們常說(shuō)的 Fiber 對(duì)象就是 React 中,用以存儲(chǔ)入?yún)⒑头祷亟Y(jié)果的緩存對(duì)象。這里需要注意的是,和虛擬 DOM 不同,F(xiàn)iber 是一種運(yùn)行時(shí)上下文,他的字段中記錄了大量節(jié)點(diǎn)在運(yùn)行過(guò)程中的狀態(tài)。

function FiberNode(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode) {
  // 靜態(tài)數(shù)據(jù)結(jié)構(gòu)
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null; // 指向真實(shí) DOM 對(duì)象

  // 構(gòu)建 Fiber 樹的指針
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  // 存儲(chǔ)更新與狀態(tài)
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;
  this.mode = mode;

  // 存儲(chǔ)副作用回調(diào)
  this.effectTag = NoEffect;
  this.nextEffect = null;
  this.firstEffect = null;
  this.lastEffect = null;

  // 優(yōu)先級(jí)
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  // 復(fù)用節(jié)點(diǎn)
  this.alternate = null;
}

其中,如下幾個(gè)字段,是構(gòu)成 Fiber 鏈表的重要字段。

// 構(gòu)建 Fiber 樹的指針
this.return = null;
this.child = null;
this.sibling = null;

其中,this.return 指向父節(jié)點(diǎn)。

this.child 指向子元素的第一個(gè)節(jié)點(diǎn)。

this.sibling 指向兄弟節(jié)點(diǎn)。

三、深度有限遍歷

在代碼中,我們的完整應(yīng)用整合起來(lái)大概長(zhǎng)這樣。

<App>
  <Header />
  <Sider />
  <Content />
  <Footer />
</App>

當(dāng)然,這只是語(yǔ)法糖,實(shí)際上他的代碼是這樣運(yùn)行的。

function App() {
  return (
    <>
      {Header()}
      {Sider()}
      {Content()}
      {Footer()}
    </>
  )
}

因此,節(jié)點(diǎn)的執(zhí)行過(guò)程,實(shí)際上就是一個(gè)函數(shù)的正常執(zhí)行過(guò)程。他需要滿足函數(shù)調(diào)用棧的執(zhí)行順序。也就是深度優(yōu)先

四、更新機(jī)制

React 的每一次更新,都是全量更新。因此,他的執(zhí)行,都是從最頂層的根節(jié)點(diǎn)開(kāi)始往下執(zhí)行。這也是 React 被普遍認(rèn)為性能差的核心原因。

?

但是實(shí)際上,充分利用好 React 的 diff 規(guī)則,是可以寫出元素級(jí)別的細(xì)粒度更新的高性能代碼的。只是這對(duì)開(kāi)發(fā)者的要求非常高,很少有開(kāi)發(fā)者能夠充分理解并運(yùn)用 diff 規(guī)則。

因此,當(dāng)我們明白了這種更新機(jī)制之后,在源碼中,就很容易找到每一次更新的起點(diǎn)位置。

五、diff 起點(diǎn)

每一次的更新,都是從根節(jié)點(diǎn)開(kāi)始,該位置在 ReactFiberWorkLoop.js 中。

方法名為 performWorkOnRoot。

?

在之前的版本中,并發(fā)更新的方法名為 performConcurrentWorkOnRoot,同步更新的方法名為 performSyncWorkOnRoot。

export function performWorkOnRoot(
  root: FiberRoot,
  lanes: Lanes,
  forceSync: boolean,
): void {
  const shouldTimeSlice =
    !forceSync &&
    !includesBlockingLane(lanes) &&
    !includesExpiredLane(root, lanes);
    
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
  ...
  ensureRootIsScheduled(root);
}

其中,renderRootConcurrent 會(huì)啟動(dòng) workLoopConcurrent 循環(huán)。

renderRootSync,該方法會(huì)啟動(dòng) workLoopSync 循環(huán)。

// The fiber we're working on
let workInProgress: Fiber | null = null;

// workInProgress 起始值為根節(jié)點(diǎn)
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
/** @noinline */
function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    // $FlowFixMe[incompatible-call] found when upgrading Flow
    performUnitOfWork(workInProgress);
  }
}

workLoopSync 的邏輯非常簡(jiǎn)單,就是開(kāi)始對(duì) Fiber 節(jié)點(diǎn)進(jìn)行遍歷。

// The work loop is an extremely hot path. Tell Closure not to inline it.
/** @noinline */
function workLoopSync() {
  // Perform work without checking if we need to yield between fiber.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

他們的差別就是是否支持在循環(huán)過(guò)程中,被 shouldYield() 中斷循環(huán)的執(zhí)行。最終他們都會(huì)執(zhí)行 performUnitOfWork。

這里很核心的一個(gè)知識(shí)點(diǎn)就是,workInProgress 是一個(gè)全局上下文變量,他的值會(huì)在執(zhí)行的過(guò)程中不斷發(fā)生變化。許多人會(huì)因?yàn)樵S多文章中提到的雙緩存機(jī)制對(duì)該變量產(chǎn)生誤解。實(shí)際上,他指的是當(dāng)前正在被比較的節(jié)點(diǎn)。而不僅僅只是 Fiber 樹的起點(diǎn)。我們會(huì)在后續(xù)的分析中,看到他不停的被改變,然后執(zhí)行下一個(gè)節(jié)點(diǎn)。

從邏輯中我們可以得出結(jié)論,當(dāng)最終沒(méi)有節(jié)點(diǎn)時(shí),workInProgress = null,循環(huán)才會(huì)結(jié)束。

我們注意關(guān)注接下來(lái)的 performUnitOfWork 方法。

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

我們要把 current 與 workInProgress 理解成為一個(gè)一直會(huì)移動(dòng)的指針,他們總是指向當(dāng)前正在執(zhí)行的 Fiber 節(jié)點(diǎn)。當(dāng)前節(jié)點(diǎn)執(zhí)行完之后,我們就會(huì)在修改 workInProgress 的值。

核心的代碼是下面這兩句。

next = beginWork(current, unitOfWork, subtreeRenderLanes);
workInProgress = next;

其中,current 表示已經(jīng)上一次緩存的 Fiber 節(jié)點(diǎn),workInProgress 表示當(dāng)前構(gòu)建的 Fiber 節(jié)點(diǎn)。

了解這個(gè)循環(huán)過(guò)程,是關(guān)鍵。希望我這樣解釋之后,大家都能夠完整的理解到我想要傳達(dá)的含義。

六、beginWork 的作用

這里需要特別注意的是,beginWork 是利用當(dāng)前節(jié)點(diǎn),去計(jì)算下一個(gè)節(jié)點(diǎn)。因此我們要特別關(guān)注他的入?yún)⒑头祷刂?。才能夠更加?zhǔn)確的理解 diff 的原理。

next = beginWork(current, unitOfWork, subtreeRenderLanes);

在 beginWork 的執(zhí)行中,會(huì)優(yōu)先比較當(dāng)前節(jié)點(diǎn)的 props 與 context,來(lái)決定是否需要復(fù)用下一個(gè)節(jié)點(diǎn)。注意理解這句話,可能跟我們常規(guī)的理念很不一樣。這也是準(zhǔn)確理解 React diff 的關(guān)鍵。

我們會(huì)在后續(xù)的代碼中觀察這一邏輯?,F(xiàn)在先來(lái)看一下 beginWork 中的代碼和比較邏輯。

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      // If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).
      didReceiveUpdate = true;
    } else {
      // Neither props nor legacy context changes. Check if there's a pending
      // update or context change.
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (
        !hasScheduledUpdateOrContext &&
        // If this is the second pass of an error or suspense boundary, there
        // may not be work scheduled on `current`, so we check for this flag.
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        // No pending updates or context. Bail out now.
        didReceiveUpdate = false;
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      ...

這里的關(guān)鍵是一個(gè)名為 didReceiveUpdate 的全局上下文變量。該變量用于標(biāo)記當(dāng)前 fiber 節(jié)點(diǎn)是否需要復(fù)用其子 fiber 節(jié)點(diǎn)。

其中 props 與 context 的比較代碼如下:

if (
  oldProps !== newProps ||
  hasLegacyContextChanged() ||
  // Force a re-render if the implementation changed due to hot reload:
  (__DEV__ ? workInProgress.type !== current.type : false)
) {
  ...
}

然后這里還有一個(gè)關(guān)鍵比較函數(shù)  checkScheduledUpdateOrContext,該函數(shù)用于比較是否存在 update 與 context 的變化。

function checkScheduledUpdateOrContext(
  current: Fiber,
  renderLanes: Lanes,
): boolean {
  // Before performing an early bailout, we must check if there are pending
  // updates or context.
  const updateLanes = current.lanes;
  if (includesSomeLane(updateLanes, renderLanes)) {
    return true;
  }
  // No pending update, but because context is propagated lazily, we need
  // to check for a context change before we bail out.
  if (enableLazyContextPropagation) {
    const dependencies = current.dependencies;
    if (dependencies !== null && checkIfContextChanged(dependencies)) {
      return true;
    }
  }
  return false;
}

這里很難理解的地方在于 state 的比較是如何發(fā)生的。簡(jiǎn)單說(shuō)一下,當(dāng)我們?cè)趧傞_(kāi)始調(diào)用 dispatchReducerAction 等函數(shù)觸發(fā)更新時(shí),都會(huì)提前給被影響的 fiber 節(jié)點(diǎn)標(biāo)記更新優(yōu)先級(jí)。然后再通過(guò) scheduleUpdateOnFiber 進(jìn)入后續(xù)的調(diào)度更新流程。

例如這樣:

function dispatchReducerAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
): void {
  ...
  const lane = requestUpdateLane(fiber);
  ...
  scheduleUpdateOnFiber(root, fiber, lane);

因此,我們可以通過(guò) includesSomeLane 方法來(lái)比較前后兩次 fiber 節(jié)點(diǎn)的優(yōu)先級(jí)是否發(fā)生了變化來(lái)判斷是否存在更新。

didReceiveUpdate 的值的變化非常重要,除了在 beginWork 執(zhí)行的時(shí)候,我們比較了 props 和 context 之外,在前置的邏輯中,還設(shè)定一個(gè)了一個(gè)方法用于設(shè)置他的值為 true。

export function markWorkInProgressReceivedUpdate() {
  didReceiveUpdate = true;
}

該方法被運(yùn)用在 state 的比較結(jié)果中。

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  return updateReducerImpl(hook, ((currentHook: any): Hook), reducer);
}

function updateReducerImpl<S, A>(
  hook: Hook,
  current: Hook,
  reducer: (S, A) => S,
): [S, Dispatch<A>] {
  const queue = hook.queue;
  ...
  // Mark that the fiber performed work, but only if the new state is
  // different from the current state.
  if (!is(newState, hook.memoizedState)) {
    markWorkInProgressReceivedUpdate();
  }
  ...
}

當(dāng) state、props、context 的比較結(jié)果都沒(méi)有發(fā)生變化時(shí),表示此時(shí)沒(méi)有更新發(fā)生,因此會(huì)直接進(jìn)入 bailout。

// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
  current,
  workInProgress,
  renderLanes,
);

后續(xù)調(diào)用的方法為 bailoutOnAlreadyFinishedWork,會(huì)返回當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)進(jìn)行復(fù)用,重點(diǎn)關(guān)注下面的代碼。

function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  ...
  // This fiber doesn't have work, but its subtree does. Clone the child
  // fibers and continue.
  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}

如果比較結(jié)果是無(wú)法復(fù)用,那么就會(huì)根據(jù)不同的 tag 執(zhí)行不同的創(chuàng)建函數(shù)。

switch (workInProgress.tag) {
  case LazyComponent: {
    const elementType = workInProgress.elementType;
    return mountLazyComponent(
      current,
      workInProgress,
      elementType,
      renderLanes,
    );
  }
  case FunctionComponent: {
    const Component = workInProgress.type;
    const unresolvedProps = workInProgress.pendingProps;
    const resolvedProps =
      disableDefaultPropsExceptForClasses ||
      workInProgress.elementType === Component
        ? unresolvedProps
        : resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
    return updateFunctionComponent(
      current,
      workInProgress,
      Component,
      resolvedProps,
      renderLanes,
    );
  }
  case ClassComponent: {
    const Component = workInProgress.type;
    const unresolvedProps = workInProgress.pendingProps;
    const resolvedProps = resolveClassComponentProps(
      Component,
      unresolvedProps,
      workInProgress.elementType === Component,
    );
    return updateClassComponent(
      current,
      workInProgress,
      Component,
      resolvedProps,
      renderLanes,
    );
  }
  ...
}

我們重點(diǎn)關(guān)注 updateFunctionComponent,并重點(diǎn)關(guān)注如下幾行代碼。

function updateFunctionComponent(
  current: null | Fiber,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
  ...
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

reconcileChildren 中會(huì)調(diào)用 reconcileChildFibers 方法,該方法則可以被稱為是子節(jié)點(diǎn) diff 的入口函數(shù)。他會(huì)根據(jù) newChild 的不同類型做不同的處理。

function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // Handle object types
    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        case REACT_PORTAL_TYPE:
          return placeSingleChild(
            reconcileSinglePortal(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        case REACT_LAZY_TYPE:
          const payload = newChild._payload;
          const init = newChild._init;
          // TODO: This function is supposed to be non-recursive.
          return reconcileChildFibers(
            returnFiber,
            currentFirstChild,
            init(payload),
            lanes,
          );
      }

      if (isArray(newChild)) {
        return reconcileChildrenArray(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        );
      }

      if (getIteratorFn(newChild)) {
        return reconcileChildrenIterator(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        );
      }

      throwOnInvalidObjectType(returnFiber, newChild);
    }

    if (
      (typeof newChild === 'string' && newChild !== '') ||
      typeof newChild === 'number'
    ) {
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          '' + newChild,
          lanes,
        ),
      );
    }

    if (__DEV__) {
      if (typeof newChild === 'function') {
        warnOnFunctionType(returnFiber);
      }
    }

    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

  return reconcileChildFibers;
}

子節(jié)點(diǎn)的類型非常多,每一種類型如何處理我們都要單獨(dú)去判斷。我們這里以其中一個(gè)單元素節(jié)點(diǎn)為例 reconcileSingleElement 來(lái)繼續(xù)分析。他的代碼如下:

function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  const key = element.key;
  let child = currentFirstChild;
  while (child !== null) {
    // TODO: If key === null and child.key === null, then this only applies to
    // the first item in the list.
    if (child.key === key) {
      const elementType = element.type;
      if (elementType === REACT_FRAGMENT_TYPE) {
        if (child.tag === Fragment) {
          deleteRemainingChildren(returnFiber, child.sibling);
          const existing = useFiber(child, element.props.children);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
      } else {
        if (
          child.elementType === elementType ||
          // Keep this check inline so it only runs on the false path:
          (__DEV__
            ? isCompatibleFamilyForHotReloading(child, element)
            : false) ||
          // Lazy types should reconcile their resolved type.
          // We need to do this after the Hot Reloading check above,
          // because hot reloading has different semantics than prod because
          // it doesn't resuspend. So we can't let the call below suspend.
          (typeof elementType === 'object' &&
            elementType !== null &&
            elementType.$$typeof === REACT_LAZY_TYPE &&
            resolveLazy(elementType) === child.type)
        ) {
          deleteRemainingChildren(returnFiber, child.sibling);
          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;
        }
      }
      // Didn't match.
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }

  if (element.type === REACT_FRAGMENT_TYPE) {
    const created = createFiberFromFragment(
      element.props.children,
      returnFiber.mode,
      lanes,
      element.key,
    );
    created.return = returnFiber;
    return created;
  } else {
    const created = createFiberFromElement(element, returnFiber.mode, lanes);
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}

在代碼中我們可以看到,這里會(huì)以此比較 key、type、tag 的值。如果都相同,則選擇復(fù)用,返回已經(jīng)存在的節(jié)點(diǎn)。

const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
return existing;

到這里,diff 的整個(gè)流程都已經(jīng)比較清楚了。在理解 diff 時(shí),我們對(duì) Fiber 的鏈表結(jié)構(gòu)足夠清晰,明確兩個(gè)指針 current 與 workInProgress 的含義與移動(dòng)方向,理解起來(lái)就會(huì)非常容易。

七、總結(jié)

由于時(shí)間有限,文字的表達(dá)能力有限,本文并沒(méi)有完全的從結(jié)論的角度為大家介紹 diff 算法是如何如何。而是從源碼的角度引導(dǎo)大家應(yīng)該如何在代碼中去自己提煉相關(guān)的觀點(diǎn)。因此,主要的表訴方式是以大概的實(shí)現(xiàn)思路加上源碼的函數(shù)調(diào)用路徑來(lái)跟大家分享。

責(zé)任編輯:姜華 來(lái)源: 這波能反殺
相關(guān)推薦

2022-04-15 08:07:21

ReactDiff算法

2022-12-07 11:21:30

Reactdiff

2022-08-14 23:04:54

React前端框架

2022-06-28 15:13:12

Vuediff 算法

2021-03-05 11:49:03

React代碼運(yùn)算符

2023-05-26 14:08:00

Where 條件MySQL

2021-02-26 10:46:11

接口測(cè)試DiffUnix系統(tǒng)

2016-10-26 20:49:24

ReactJavascript前端

2020-02-21 10:30:10

開(kāi)發(fā)技能代碼

2022-07-31 19:57:26

react項(xiàng)目VSCode

2020-06-02 10:10:46

React前端組件

2022-09-09 19:01:02

接口Reader?Spark

2023-04-17 08:19:47

select *MySQL

2021-08-31 07:02:20

Diff算法DOM

2021-07-06 07:27:45

React元素屬性

2019-02-21 23:36:09

源碼框架讀源碼

2019-11-28 14:07:46

技術(shù)架構(gòu)代碼

2021-02-11 13:30:56

Nodejs源碼c++

2023-04-10 08:07:48

MySQLlimitoffset

2010-05-04 17:05:29

DNS負(fù)載均衡
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)