欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

react源碼層分析協(xié)調(diào)與調(diào)度

 更新時間:2022年10月24日 11:28:33   作者:flyzz177  
本文主要介紹了深入理解React協(xié)調(diào)與調(diào)度(Scheduler)原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧

requestEventTime

其實在React執(zhí)行過程中,會有數(shù)不清的任務要去執(zhí)行,但是他們會有一個優(yōu)先級的判定,假如兩個事件的優(yōu)先級一樣,那么React是怎么去判定他們兩誰先執(zhí)行呢?

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
export function requestEventTime() {
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    // We're inside React, so it's fine to read the actual time.
    // react事件正在執(zhí)行
    // executionContext
    // RenderContext 正在計算
    // CommitContext 正在提交
    // export const NoContext = /*             */ 0b0000000;
    // const BatchedContext = /*               */ 0b0000001;
    // const EventContext = /*                 */ 0b0000010;
    // const DiscreteEventContext = /*         */ 0b0000100;
    // const LegacyUnbatchedContext = /*       */ 0b0001000;
    // const RenderContext = /*                */ 0b0010000;
    // const CommitContext = /*                */ 0b0100000;
    // export const RetryAfterError = /*       */ 0b1000000;
    return now();
  }
  // 沒有在react事件執(zhí)行 NoTimestamp === -1
  if (currentEventTime !== NoTimestamp) { 
    // 瀏覽器事件正在執(zhí)行,返回上次的 currentEventTime
    return currentEventTime;
  }
  // 重新計算currentEventTime,當執(zhí)行被中斷后
  currentEventTime = now();
  return currentEventTime;
}
  • RenderContextCommitContext表示正在計算更新和正在提交更新,返回now()。
  • 如果是瀏覽器事件正在執(zhí)行中,返回上一次的currentEventTime。
  • 如果終止或者中斷react任務執(zhí)行的時候,則重新獲取執(zhí)行時間now()。
  • 獲取的時間越小,則執(zhí)行的優(yōu)先級越高。

now()并不是單純的new Date(),而是判定兩次更新任務的時間是否小于10ms,來決定是否復用上一次的更新時間Scheduler_now的。

export const now = initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;

其實各位猜想一下,對于10ms級別的任務間隙時間,幾乎是可以忽略不計的,那么這里就可以視為同樣的任務,不需要有很大的性能開銷,有利于批量更新。

requestUpdateLane

requestEventTime位每一個需要執(zhí)行的任務打上了觸發(fā)更新時間標簽,那么任務的優(yōu)先級還需要進一步的確立,requestUpdateLane就是用來獲取每一個任務執(zhí)行的優(yōu)先級的。

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
export function requestUpdateLane(fiber: Fiber): Lane {
  // Special cases
  const mode = fiber.mode;
  if ((mode & BlockingMode) === NoMode) {
    return (SyncLane: Lane);
  } else if ((mode & ConcurrentMode) === NoMode) {
    return getCurrentPriorityLevel() === ImmediateSchedulerPriority
      ? (SyncLane: Lane)
      : (SyncBatchedLane: Lane);
  } else if (
    !deferRenderPhaseUpdateToNextBatch &&
    (executionContext & RenderContext) !== NoContext &&
    workInProgressRootRenderLanes !== NoLanes
  ) {
    // This is a render phase update. These are not officially supported. The
    // old behavior is to give this the same "thread" (expiration time) as
    // whatever is currently rendering. So if you call `setState` on a component
    // that happens later in the same render, it will flush. Ideally, we want to
    // remove the special case and treat them as if they came from an
    // interleaved event. Regardless, this pattern is not officially supported.
    // This behavior is only a fallback. The flag only exists until we can roll
    // out the setState warning, since existing code might accidentally rely on
    // the current behavior.
    return pickArbitraryLane(workInProgressRootRenderLanes);
  }
  // The algorithm for assigning an update to a lane should be stable for all
  // updates at the same priority within the same event. To do this, the inputs
  // to the algorithm must be the same. For example, we use the `renderLanes`
  // to avoid choosing a lane that is already in the middle of rendering.
  //
  // However, the "included" lanes could be mutated in between updates in the
  // same event, like if you perform an update inside `flushSync`. Or any other
  // code path that might call `prepareFreshStack`.
  //
  // The trick we use is to cache the first of each of these inputs within an
  // event. Then reset the cached values once we can be sure the event is over.
  // Our heuristic for that is whenever we enter a concurrent work loop.
  //
  // We'll do the same for `currentEventPendingLanes` below.
  if (currentEventWipLanes === NoLanes) {
    currentEventWipLanes = workInProgressRootIncludedLanes;
  }
  const isTransition = requestCurrentTransition() !== NoTransition;
  if (isTransition) {
    if (currentEventPendingLanes !== NoLanes) {
      currentEventPendingLanes =
        mostRecentlyUpdatedRoot !== null
          ? mostRecentlyUpdatedRoot.pendingLanes
          : NoLanes;
    }
    return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
  }
  // TODO: Remove this dependency on the Scheduler priority.
  // To do that, we're replacing it with an update lane priority.
  // 獲取執(zhí)行任務的優(yōu)先級,便于調(diào)度
  const schedulerPriority = getCurrentPriorityLevel();
  // The old behavior was using the priority level of the Scheduler.
  // This couples React to the Scheduler internals, so we're replacing it
  // with the currentUpdateLanePriority above. As an example of how this
  // could be problematic, if we're not inside `Scheduler.runWithPriority`,
  // then we'll get the priority of the current running Scheduler task,
  // which is probably not what we want.
  let lane;
  if (
    // TODO: Temporary. We're removing the concept of discrete updates.
    (executionContext & DiscreteEventContext) !== NoContext &&

    // 用戶block的類型事件
    schedulerPriority === UserBlockingSchedulerPriority
  ) {
    // 通過findUpdateLane函數(shù)重新計算lane
    lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
  } else {
    // 根據(jù)優(yōu)先級計算法則計算lane
    const schedulerLanePriority = schedulerPriorityToLanePriority(
      schedulerPriority,
    );
    if (decoupleUpdatePriorityFromScheduler) {
      // In the new strategy, we will track the current update lane priority
      // inside React and use that priority to select a lane for this update.
      // For now, we're just logging when they're different so we can assess.
      const currentUpdateLanePriority = getCurrentUpdateLanePriority();

      if (
        schedulerLanePriority !== currentUpdateLanePriority &&
        currentUpdateLanePriority !== NoLanePriority
      ) {
        if (__DEV__) {
          console.error(
            'Expected current scheduler lane priority %s to match current update lane priority %s',
            schedulerLanePriority,
            currentUpdateLanePriority,
          );
        }
      }
    }
    // 根據(jù)計算得到的 schedulerLanePriority,計算更新的優(yōu)先級 lane
    lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
  }
  return lane;
}
  • 通過getCurrentPriorityLevel獲得所有執(zhí)行任務的調(diào)度優(yōu)先級schedulerPriority。
  • 通過findUpdateLane計算lane,作為更新中的優(yōu)先級。

findUpdateLane

export function findUpdateLane(
  lanePriority: LanePriority,  wipLanes: Lanes,
): Lane {
  switch (lanePriority) {
    case NoLanePriority:
      break;
    case SyncLanePriority:
      return SyncLane;
    case SyncBatchedLanePriority:
      return SyncBatchedLane;
    case InputDiscreteLanePriority: {
      const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
      if (lane === NoLane) {
        // Shift to the next priority level
        return findUpdateLane(InputContinuousLanePriority, wipLanes);
      }
      return lane;
    }
    case InputContinuousLanePriority: {
      const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
      if (lane === NoLane) {
        // Shift to the next priority level
        return findUpdateLane(DefaultLanePriority, wipLanes);
      }
      return lane;
    }
    case DefaultLanePriority: {
      let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
      if (lane === NoLane) {
        // If all the default lanes are already being worked on, look for a
        // lane in the transition range.
        lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
        if (lane === NoLane) {
          // All the transition lanes are taken, too. This should be very
          // rare, but as a last resort, pick a default lane. This will have
          // the effect of interrupting the current work-in-progress render.
          lane = pickArbitraryLane(DefaultLanes);
        }
      }
      return lane;
    }
    case TransitionPriority: // Should be handled by findTransitionLane instead
    case RetryLanePriority: // Should be handled by findRetryLane instead
      break;
    case IdleLanePriority:
      let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
      if (lane === NoLane) {
        lane = pickArbitraryLane(IdleLanes);
      }
      return lane;
    default:
      // The remaining priorities are not valid for updates
      break;
  }
  invariant(
    false,
    'Invalid update priority: %s. This is a bug in React.',
    lanePriority,
  );
}

相關參考視頻講解:進入學習

lanePriority LanePriority

export opaque type LanePriority =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17;
export opaque type Lanes = number;
export opaque type Lane = number;
export opaque type LaneMap<T> = Array<T>;
import {
  ImmediatePriority as ImmediateSchedulerPriority,
  UserBlockingPriority as UserBlockingSchedulerPriority,
  NormalPriority as NormalSchedulerPriority,
  LowPriority as LowSchedulerPriority,
  IdlePriority as IdleSchedulerPriority,
  NoPriority as NoSchedulerPriority,
} from './SchedulerWithReactIntegration.new';
// 同步任務
export const SyncLanePriority: LanePriority = 15;
export const SyncBatchedLanePriority: LanePriority = 14;
// 用戶事件
const InputDiscreteHydrationLanePriority: LanePriority = 13;
export const InputDiscreteLanePriority: LanePriority = 12;
const InputContinuousHydrationLanePriority: LanePriority = 11;
export const InputContinuousLanePriority: LanePriority = 10;
const DefaultHydrationLanePriority: LanePriority = 9;
export const DefaultLanePriority: LanePriority = 8;
const TransitionHydrationPriority: LanePriority = 7;
export const TransitionPriority: LanePriority = 6;
const RetryLanePriority: LanePriority = 5;
const SelectiveHydrationLanePriority: LanePriority = 4;
const IdleHydrationLanePriority: LanePriority = 3;
const IdleLanePriority: LanePriority = 2;
const OffscreenLanePriority: LanePriority = 1;
export const NoLanePriority: LanePriority = 0;

createUpdate

export function createUpdate(eventTime: number, lane: Lane): Update<*> {
  const update: Update<*> = {
    eventTime, // 更新時間
    lane, // 優(yōu)先級
    tag: UpdateState,//更新
    payload: null,// 需要更新的內(nèi)容
    callback: null, // 更新完后的回調(diào)
    next: null, // 指向下一個更新
  };
  return update;
}

createUpdate函數(shù)入?yún)?code>eventTime和lane,輸出一個update對象,而對象中的tag表示此對象要進行什么樣的操作。

export const UpdateState = 0;// 更新
export const ReplaceState = 1;//替換
export const ForceUpdate = 2;//強制更新
export const CaptureUpdate = 3;//xx更新

createUpdate就是單純的給每一個任務進行包裝,作為一個個體推入到更新隊列中。

enqueueUpdate

export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  // 獲取當前更新隊列?為啥呢?因為無法保證react是不是還有正在更新或者沒有更新完畢的任務
  const updateQueue = fiber.updateQueue;
  //  如果更新隊列為空,則表示fiber還未渲染,直接退出
  if (updateQueue === null) {
    // Only occurs if the fiber has been unmounted.
    return;
  }
  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
  const pending = sharedQueue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
     // 還記得那個更新對象嗎?update.next =>
     // 如果pedding位null,表示第一次渲染,那么他的指針為update本身
    update.next = update;
  } else {
    // 將update插入到更新隊列循環(huán)當中
    update.next = pending.next;
    pending.next = update;
  }
  sharedQueue.pending = update;
  if (__DEV__) {
    if (
      currentlyProcessingQueue === sharedQueue &&
      !didWarnUpdateInsideUpdate
    ) {
      console.error(
        'An update (setState, replaceState, or forceUpdate) was scheduled ' +
          'from inside an update function. Update functions should be pure, ' +
          'with zero side-effects. Consider using componentDidUpdate or a ' +
          'callback.',
      );
      didWarnUpdateInsideUpdate = true;
    }
  }
}

這一步就是把需要更新的對象,與fiber更新隊列關聯(lián)起來。

總結(jié)

React通過獲取事件的優(yōu)先級,處理具有同樣優(yōu)先級的事件,創(chuàng)建更新對象并與fiber的更新隊列關聯(lián)起來。到這一步updateContainer這個流程就走完了,也下面就是開始他的協(xié)調(diào)階段了。

協(xié)調(diào)與調(diào)度

協(xié)調(diào)與調(diào)度的流程大致如圖所示:

reconciler流程

Reactreconciler流程以scheduleUpdateOnFiber為入口,并在checkForNestedUpdates里面處理任務更新的嵌套層數(shù),如果嵌套層數(shù)過大( >50 ),就會認為是無效更新,則會拋出異常。之后便根據(jù)markUpdateLaneFromFiberToRoot對當前的fiber樹,自底向上的遞歸fiberlane,根據(jù)lane做二進制比較或者位運算處理。詳情如下:

  • 如果當前執(zhí)行任務的優(yōu)先級為同步,則去判斷有無正在執(zhí)行的React任務。如果沒有則執(zhí)行ensureRootIsScheduled,進行調(diào)度處理。
  • 如果當前任務優(yōu)先級是異步執(zhí)行,則執(zhí)行ensureRootIsScheduled進行調(diào)度處理。
export function scheduleUpdateOnFiber(
  fiber: Fiber,  lane: Lane,  eventTime: number,
) {
  // 檢查嵌套層數(shù),避免是循環(huán)做無效操作
  checkForNestedUpdates();
  warnAboutRenderPhaseUpdatesInDEV(fiber);
  // 更新當前更新隊列里面的任務優(yōu)先級,自底而上更新child.fiberLanes
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return null;
  }
  // Mark that the root has a pending update.
  // 標記root有更新的,執(zhí)行它
  markRootUpdated(root, lane, eventTime);
  if (root === workInProgressRoot) {
    // Received an update to a tree that's in the middle of rendering. Mark
    // that there was an interleaved update work on this root. Unless the
    // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
    // phase update. In that case, we don't treat render phase updates as if
    // they were interleaved, for backwards compat reasons.
    if (
      deferRenderPhaseUpdateToNextBatch ||
      (executionContext & RenderContext) === NoContext
    ) {
      workInProgressRootUpdatedLanes = mergeLanes(
        workInProgressRootUpdatedLanes,
        lane,
      );
    }
    if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
      // The root already suspended with a delay, which means this render
      // definitely won't finish. Since we have a new update, let's mark it as
      // suspended now, right before marking the incoming update. This has the
      // effect of interrupting the current render and switching to the update.
      // TODO: Make sure this doesn't override pings that happen while we've
      // already started rendering.
      markRootSuspended(root, workInProgressRootRenderLanes);
    }
  }
  // TODO: requestUpdateLanePriority also reads the priority. Pass the
  // priority as an argument to that function and this one.
  // 獲取當前優(yōu)先級層次
  const priorityLevel = getCurrentPriorityLevel();
  // 同步任務,采用同步更新的方式
  if (lane === SyncLane) {
    if (
      // Check if we're inside unbatchedUpdates
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      // Check if we're not already rendering
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // Register pending interactions on the root to avoid losing traced interaction data.
      // 同步而且沒有react任務在執(zhí)行,調(diào)用performSyncWorkOnRoot
      schedulePendingInteractions(root, lane);
      // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
      // root inside of batchedUpdates should be synchronous, but layout updates
      // should be deferred until the end of the batch.
      performSyncWorkOnRoot(root);
    } else {
      // 如果有正在執(zhí)行的react任務,那么執(zhí)行它ensureRootIsScheduled去復用當前正在執(zhí)行的任務
      // 跟本次更新一起進行
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
      if (executionContext === NoContext) {
        // Flush the synchronous work now, unless we're already working or inside
        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
        // scheduleCallbackForFiber to preserve the ability to schedule a callback
        // without immediately flushing it. We only do this for user-initiated
        // updates, to preserve historical behavior of legacy mode.
        resetRenderTimer();
        flushSyncCallbackQueue();
      }
    }
  } else {
    // Schedule a discrete update but only if it's not Sync.
    // 如果此次是異步任務
    if (
      (executionContext & DiscreteEventContext) !== NoContext &&
      // Only updates at user-blocking priority or greater are considered
      // discrete, even inside a discrete event.
      (priorityLevel === UserBlockingSchedulerPriority ||
        priorityLevel === ImmediateSchedulerPriority)
    ) {
      // This is the result of a discrete event. Track the lowest priority
      // discrete update per root so we can flush them early, if needed.
      if (rootsWithPendingDiscreteUpdates === null) {
        rootsWithPendingDiscreteUpdates = new Set([root]);
      } else {
        rootsWithPendingDiscreteUpdates.add(root);
      }
    }
    // Schedule other updates after in case the callback is sync.
    // 可以中斷更新,只要調(diào)用ensureRootIsScheduled => performConcurrentWorkOnRoot
    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);
  }
  // We use this when assigning a lane for a transition inside
  // `requestUpdateLane`. We assume it's the same as the root being updated,
  // since in the common case of a single root app it probably is. If it's not
  // the same root, then it's not a huge deal, we just might batch more stuff
  // together more than necessary.
  mostRecentlyUpdatedRoot = root;
}

同步任務類型執(zhí)行機制

當任務的類型為同步任務,并且當前的js主線程空閑,會通過 performSyncWorkOnRoot(root) 方法開始執(zhí)行同步任務。

performSyncWorkOnRoot 里面主要做了兩件事:

  • renderRootSync 從根節(jié)點開始進行同步渲染任務
  • commitRoot 執(zhí)行 commit 流程

當前js線程中有正在執(zhí)行的任務時候,就會觸發(fā)ensureRootIsScheduled函數(shù)。 ensureRootIsScheduled里面主要是處理當前加入的更新任務的lane是否有變化:

  • 如果沒有變化則表示跟當前的schedule一起執(zhí)行。
  • 如果有則創(chuàng)建新的schedule。
  • 調(diào)用performSyncWorkOnRoot執(zhí)行同步任務。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode;
  // Check if any lanes are being starved by other work. If so, mark them as
  // expired so we know to work on those next.
  markStarvedLanesAsExpired(root, currentTime);
  // Determine the next lanes to work on, and their priority.
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  // This returns the priority level computed during the `getNextLanes` call.
  const newCallbackPriority = returnNextLanesPriority();
  if (nextLanes === NoLanes) {
    // Special case: There's nothing to work on.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
      root.callbackNode = null;
      root.callbackPriority = NoLanePriority;
    }
    return;
  }
  // Check if there's an existing task. We may be able to reuse it.
  if (existingCallbackNode !== null) {
    const existingCallbackPriority = root.callbackPriority;
    if (existingCallbackPriority === newCallbackPriority) {
      // The priority hasn't changed. We can reuse the existing task. Exit.
      return;
    }
    // The priority changed. Cancel the existing callback. We'll schedule a new
    // one below.
    cancelCallback(existingCallbackNode);
  }
  // Schedule a new callback.
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    // Special case: Sync React callbacks are scheduled on a special
    // internal queue
    // 同步任務調(diào)用performSyncWorkOnRoot
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    // 異步任務調(diào)用 performConcurrentWorkOnRoot
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

所以任務類型為同步的時候,不管js線程空閑與否,都會走到performSyncWorkOnRoot,進而走renderRootSync、workLoopSync流程,而在workLoopSync中,只要workInProgress fiber不為null,則會一直循環(huán)執(zhí)行performUnitOfWork,而performUnitOfWork中會去執(zhí)行beginWorkcompleteWork,也就是上一章里面說的beginWork流程去創(chuàng)建每一個fiber節(jié)點

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

異步任務類型執(zhí)行機制

異步任務則會去執(zhí)行performConcurrentWorkOnRoot,進而去執(zhí)行renderRootConcurrent、workLoopConcurrent,但是與同步任務不同的是異步任務是可以中斷的,這個可中斷的關鍵字就在于shouldYield,它本身返回值是一個false,為true則可以中斷。

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

每一次在執(zhí)行performUnitOfWork之前都會關注一下shouldYield()返回值,也就是說的reconciler過程可中斷的意思。

shouldYield

// packages\scheduler\src\SchedulerPostTask.js
export function unstable_shouldYield() {
  return getCurrentTime() >= deadline;
}

getCurrentTimenew Date(),deadline為瀏覽器處理每一幀結(jié)束時間戳,所以這里表示的是,在瀏覽器每一幀空閑的時候,才會去處理此任務,如果當前任務在瀏覽器執(zhí)行的某一幀里面,則會中斷當前任務,等待瀏覽器當前幀執(zhí)行完畢,等到下一幀空閑的時候,才會去執(zhí)行當前任務。

所以不管在workLoopConcurrent還是workLoopSync中,都會根據(jù)當前的workInProgress fiber是否為null來進行循環(huán)調(diào)用performUnitOfWork。根據(jù)流程圖以及上面說的這一些,可以看得出來從beginWorkcompleteUnitOfWork這個過程究竟干了什么。

這三章將會講解fiber樹的reconcileChildren過程、completeWork過程、commitMutationEffectsinsertOrAppendPlacementNodeIntoContainer(DOM)過程。這里將詳細解讀v17版本的React的diff算法、虛擬dom到真實dom的創(chuàng)建,函數(shù)生命鉤子的執(zhí)行流程等。

performUnitOfWork

function performUnitOfWork(unitOfWork: Fiber): void {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  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 {
    // beginWork
    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
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
  ReactCurrentOwner.current = null;
}

所以在performUnitOfWork里面,每一次執(zhí)行beginWork,進行workIngProgress更新,當遍歷完畢整棵fiber樹之后便會執(zhí)行completeUnitOfWork。

beginWork

我們可以看到beginWork就是originBeginWork得實際執(zhí)行。我們翻開beginWork的源碼可以看到,它便是根據(jù)不同的workInProgress.tag執(zhí)行不同組件類型的處理函數(shù),這里就不去拆分的太細,只有有想法便會單獨出一篇文章講述這個的細節(jié),但是最后都會去調(diào)用reconcileChildren

completeUnitOfWork

當遍歷完畢執(zhí)行beginWork,遍歷完畢之后就會走completeUnitOfWork

function completeUnitOfWork(unitOfWork: Fiber): void {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
  let completedWork = unitOfWork;
  do {
    // The current, flushed, state of this fiber is the alternate. Ideally
    // nothing should rely on this, but relying on it here means that we don't
    // need an additional field on the work in progress.
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
    // Check if the work completed or if something threw.
    if ((completedWork.flags & Incomplete) === NoFlags) {
      setCurrentDebugFiberInDEV(completedWork);
      let next;
      if (
        !enableProfilerTimer ||
        (completedWork.mode & ProfileMode) === NoMode
      ) {
        // 綁定事件,更新props,更新dom
        next = completeWork(current, completedWork, subtreeRenderLanes);
      } else {
        startProfilerTimer(completedWork);
        next = completeWork(current, completedWork, subtreeRenderLanes);
        // Update render duration assuming we didn't error.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
      }
      resetCurrentDebugFiberInDEV();
      if (next !== null) {
        // Completing this fiber spawned new work. Work on that next.
        workInProgress = next;
        return;
      }
      resetChildLanes(completedWork);
      if (
        returnFiber !== null &&
        // Do not append effects to parents if a sibling failed to complete
        (returnFiber.flags & Incomplete) === NoFlags
      ) {
        // Append all the effects of the subtree and this fiber onto the effect
        // list of the parent. The completion order of the children affects the
        // side-effect order.
        // 把已收集到的副作用,合并到父級effect lists中
        if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = completedWork.firstEffect;
        }
        if (completedWork.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
          }
          returnFiber.lastEffect = completedWork.lastEffect;
        }
        // If this fiber had side-effects, we append it AFTER the children's
        // side-effects. We can perform certain side-effects earlier if needed,
        // by doing multiple passes over the effect list. We don't want to
        // schedule our own side-effect on our own list because if end up
        // reusing children we'll schedule this effect onto itself since we're
        // at the end.
        const flags = completedWork.flags;
        // Skip both NoWork and PerformedWork tags when creating the effect
        // list. PerformedWork effect is read by React DevTools but shouldn't be
        // committed.
        // 跳過NoWork,PerformedWork在commit階段用不到
        if (flags > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork;
          } else {
            returnFiber.firstEffect = completedWork;
          }
          returnFiber.lastEffect = completedWork;
        }
      }
    } else {
      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.
      const next = unwindWork(completedWork, subtreeRenderLanes);
      // Because this fiber did not complete, don't reset its expiration time.
      if (next !== null) {
        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.
        next.flags &= HostEffectMask;
        workInProgress = next;
        return;
      }
      if (
        enableProfilerTimer &&
        (completedWork.mode & ProfileMode) !== NoMode
      ) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
        // Include the time spent working on failed children before continuing.
        let actualDuration = completedWork.actualDuration;
        let child = completedWork.child;
        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }
        completedWork.actualDuration = actualDuration;
      }
      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        returnFiber.firstEffect = returnFiber.lastEffect = null;
        returnFiber.flags |= Incomplete;
      }
    }
    // 兄弟層指針
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }
    // Otherwise, return to the parent
    completedWork = returnFiber;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
  } while (completedWork !== null);
  // We've reached the root.
  if (workInProgressRootExitStatus === RootIncomplete) {
    workInProgressRootExitStatus = RootCompleted;
  }
}

他的作用便是逐層收集fiber樹上已經(jīng)被打上的副作用標簽flags,一直收集到root上面以便于在commit階段進行dom的增刪改。

scheduler流程

在這里應該有很多人不明白,協(xié)調(diào)調(diào)度是什么意思,通俗來講:

  • 協(xié)調(diào)就是協(xié)同合作
  • 調(diào)度就是執(zhí)行命令

所以在React中協(xié)調(diào)就是一個js線程中,需要安排很多模塊去完成整個流程,例如:同步異步lane的處理,reconcileChildren處理fiber節(jié)點等,保證整個流程有條不紊的執(zhí)行。調(diào)度表現(xiàn)為讓空閑的js線程(幀層面)去執(zhí)行其他任務,這個過程稱之為調(diào)度,那么它到底是怎么去做的呢?

我們回到處理異步任務那里,我們會發(fā)現(xiàn)performConcurrentWorkOnRoot這個函數(shù)外面包裹了一層scheduleCallback

newCallbackNode = scheduleCallback(
   schedulerPriorityLevel,
   performConcurrentWorkOnRoot.bind(null, root),
)
export function scheduleCallback(
  reactPriorityLevel: ReactPriorityLevel,  callback: SchedulerCallback,  options: SchedulerCallbackOptions | void | null,
) {
  const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
  return Scheduler_scheduleCallback(priorityLevel, callback, options);
}

我們幾經(jīng)周折找到了聲明函數(shù)的地方

// packages/scheduler/src/Scheduler.js
function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();
  var startTime;
  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }
  } else {
    startTime = currentTime;
  }
  var timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT;
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT;
      break;
    case NormalPriority:
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT;
      break;
  }
  var expirationTime = startTime + timeout;
  var newTask = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };
  if (enableProfiling) {
    newTask.isQueued = false;
  }
  if (startTime > currentTime) {
    // This is a delayed task.
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // All tasks are delayed, and this is the task with the earliest delay.
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // Schedule a timeout.
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);
    if (enableProfiling) {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    // Schedule a host callback, if needed. If we're already performing work,
    // wait until the next time we yield.
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }
  return newTask;
}
  • starttime > currentTime的時候,表示任務超時,插入超時隊列。
  • 任務沒有超時,插入調(diào)度隊列
  • 執(zhí)行requestHostCallback調(diào)度任務。
  // 創(chuàng)建消息通道
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  // 告知scheduler開始調(diào)度
  requestHostCallback = function(callback) {
    scheduledHostCallback = callback;
    if (!isMessageLoopRunning) {
      isMessageLoopRunning = true;
      port.postMessage(null);
    }
  };

react通過 new MessageChannel() 創(chuàng)建了消息通道,當發(fā)現(xiàn)js線程空閑時,通過postMessage通知 scheduler開始調(diào)度。performWorkUntilDeadline函數(shù)功能為處理react調(diào)度開始時間更新到結(jié)束時間。

這里我們要關注一下設備幀速率。

  forceFrameRate = function(fps) {
    if (fps < 0 || fps > 125) {
      // Using console['error'] to evade Babel and ESLint
      console['error'](
        'forceFrameRate takes a positive int between 0 and 125, ' +
          'forcing frame rates higher than 125 fps is not supported',
      );
      return;
    }
    if (fps > 0) {
      yieldInterval = Math.floor(1000 / fps);
    } else {
      // reset the framerate
      yieldInterval = 5;
    }
  };

performWorkUntilDeadline

  const performWorkUntilDeadline = () => {
    if (scheduledHostCallback !== null) {
      const currentTime = getCurrentTime();
      // Yield after `yieldInterval` ms, regardless of where we are in the vsync
      // cycle. This means there's always time remaining at the beginning of
      // the message event.
      // 更新當前幀結(jié)束時間
      deadline = currentTime + yieldInterval;
      const hasTimeRemaining = true;
      try {
        const hasMoreWork = scheduledHostCallback(
          hasTimeRemaining,
          currentTime,
        );
        // 還有任務就繼續(xù)執(zhí)行
        if (!hasMoreWork) {
          isMessageLoopRunning = false;
          scheduledHostCallback = null;
        } else {
          // If there's more work, schedule the next message event at the end
          // of the preceding one.
          // 沒有就postMessage
          port.postMessage(null);
        }
      } catch (error) {
        // If a scheduler task throws, exit the current browser task so the
        // error can be observed.
        port.postMessage(null);
        throw error;
      }
    } else {
      isMessageLoopRunning = false;
    }
    // Yielding to the browser will give it a chance to paint, so we can
    // reset this.
    needsPaint = false;
  };

總結(jié)

本文講了React在狀態(tài)改變的時候,會根據(jù)當前任務優(yōu)先級,等一些列操作去創(chuàng)建workInProgress fiber鏈表樹,在協(xié)調(diào)階段,會根據(jù)瀏覽器每一幀去做比較,假如瀏覽器每一幀執(zhí)行時間戳高于當前時間,則表示當前幀沒有空閑時間,當前任務則必須要等到下一個空閑幀才能去執(zhí)行的可中斷的策略。還有關于beginWork的遍歷執(zhí)行更新fiber的節(jié)點。那么到這里這一章就講述完畢了,下一章講一講React的diff算法

到此這篇關于react源碼層分析協(xié)調(diào)與調(diào)度的文章就介紹到這了,更多相關react協(xié)調(diào)與調(diào)度內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • React封裝彈出框組件的方法

    React封裝彈出框組件的方法

    這篇文章主要為大家詳細介紹了React封裝彈出框組件的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • react 移動端實現(xiàn)列表左滑刪除的示例代碼

    react 移動端實現(xiàn)列表左滑刪除的示例代碼

    這篇文章主要介紹了react 移動端實現(xiàn)列表左滑刪除的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-07-07
  • react源碼中的生命周期和事件系統(tǒng)實例解析

    react源碼中的生命周期和事件系統(tǒng)實例解析

    這篇文章主要為大家介紹了react源碼中的生命周期和事件系統(tǒng)實例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • React?跨端動態(tài)化核心技術實例分析

    React?跨端動態(tài)化核心技術實例分析

    這篇文章主要為大家介紹了React?跨端動態(tài)化核心技術實例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • React?實現(xiàn)具備吸頂和吸底功能組件實例

    React?實現(xiàn)具備吸頂和吸底功能組件實例

    這篇文章主要為大家介紹了React?實現(xiàn)具備吸頂和吸底功能組件實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • React報錯map()?is?not?a?function詳析

    React報錯map()?is?not?a?function詳析

    這篇文章主要介紹了React報錯map()?is?not?a?function詳析,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • react.js框架Redux基礎案例詳解

    react.js框架Redux基礎案例詳解

    這篇文章主要介紹了react.js框架Redux基礎案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-09-09
  • React Component存在的幾種形式詳解

    React Component存在的幾種形式詳解

    這篇文章主要給大家介紹了關于React Component存在的幾種形式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-11-11
  • React里的Fragment標簽的具體使用

    React里的Fragment標簽的具體使用

    本文主要介紹了React里的Fragment標簽的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-01-01
  • react中的虛擬dom和diff算法詳解

    react中的虛擬dom和diff算法詳解

    這篇文章主要介紹了react中的虛擬dom和diff算法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04

最新評論