react-dom 源码(4)scheduleWork

2,099 阅读8分钟

scheduleWork

// 函数调用栈:render > legacyRenderSubtreeIntoContainer > updateContainer > scheduleWork > scheduleUpdateOnFiber
// 传参:scheduleWork(current?1, expirationTime); 
// 所以传参 fiber 就是 rootFIber,expirationTime 是当前 fiber 的过期时间
function scheduleUpdateOnFiber(fiber, expirationTime) {
  checkForNestedUpdates();
  warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
  var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);

  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }

  checkForInterruption(fiber, expirationTime);
  recordScheduleUpdate(); 
  // TODO: computeExpirationForFiber also reads the priority. Pass the
  // priority as an argument to that function and this one.
  var priorityLevel = getCurrentPriorityLevel();

  if (expirationTime === Sync) {
    if ( 
    // Check if we're inside unbatchedUpdates
    // 检查我们是否在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.
      // 在根目录上注册待处理的交互,以避免丢失跟踪的交互数据。 
      schedulePendingInteractions(root, expirationTime); 
      // 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.
      // 在 batchedUpdates 内部, ReactDOM.render-ed 生成的 root 节点的初始安装应该是同步的
      // 而布局更新应该推迟到批处理结束。 
      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root);
      schedulePendingInteractions(root, expirationTime);

      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.
        flushSyncCallbackQueue();
      }
    }
  } else {
    ensureRootIsScheduled(root);
    schedulePendingInteractions(root, expirationTime);
  }

  if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered
  // discrete, even inside a discrete event.
  priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority)) {
    // 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 Map([[root, expirationTime]]);
    } else {
      var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);

      if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
        rootsWithPendingDiscreteUpdates.set(root, expirationTime);
      }
    }
  }
}
var scheduleWork = scheduleUpdateOnFiber;

scheduleWork 即 scheduleUpdateOnFiber

源码说明

  1. 调用 checkForNestedUpdates,检查是否有嵌套更新,如果嵌套超过50层,终止调度;
  2. 调用 markUpdateTimeFromFiberToRoot,更新 fiber、alternate 的过期时间;沿着 fiber.return 向上遍历父节点,直到找到 rootFiber,在遍历过程中更新父节点及其 alternate 的 childExpirationTime;
  3. 如果是同步更新
    • 当处于 init mount 阶段:
      • 调用 schedulePendingInteractions,在 rootFiber 上注册待处理的交互;
      • 调用 performSyncWorkOnRoot,生成 workInProgress fiber节点,启动 workLoopSync 循环,深度遍历此 fiber 所有后代子节点,并收集 effect 更新;最后调用 commitRoot 提交更新;

checkForNestedUpdates

// Use these to prevent an infinite loop of nested updates
//使用它们来防止嵌套更新的无限循环 
var NESTED_UPDATE_LIMIT = 50;
var nestedUpdateCount = 0;
var rootWithNestedUpdates = null;
var NESTED_PASSIVE_UPDATE_LIMIT = 50;
var nestedPassiveUpdateCount = 0;
// 检查嵌套更新
function checkForNestedUpdates() {
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    nestedUpdateCount = 0;
    rootWithNestedUpdates = null;

    {
      {
        throw Error("Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.");
      }
    }
  }

  {
    if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
      nestedPassiveUpdateCount = 0;
      warning$1(false, 'Maximum update depth exceeded. This can happen when a component ' + "calls setState inside useEffect, but useEffect either doesn't " + 'have a dependency array, or one of the dependencies changes on ' + 'every render.');
    }
  }
}

源码说明

作用:判断是否是无限循环的 update

  • 对超过 50 层嵌套的 update,终止进行调度,并报 error:

    超过最大更新深度。当组件在 componentWillUpdate 或 componentDidUpdate 内部重复调用 setState 时,可能会发生这种情况。 React 限制了嵌套更新的数量,以防止无限循环。

  • 对超过 50 层被动嵌套的 update,终止进行调度,并报 warning:

    超过最大更新深度。当一个组件在 useEffect 内调用 setState,但 useEffect 没有依赖项数组,或者其中一个依赖项在每次渲染都会更改时,会发生这种情况。

markUpdateTimeFromFiberToRoot

// 标记从fiber到root的更新时间
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
  // Update the source fiber's expiration time
  // 更新 fiber 的过期时间
  if (fiber.expirationTime < expirationTime) {
    fiber.expirationTime = expirationTime;
  }

  var alternate = fiber.alternate;

  if (alternate !== null && alternate.expirationTime < expirationTime) {
    alternate.expirationTime = expirationTime;
  } 
  
  // Walk the parent path to the root and update the child expiration time.
  // 向上遍历父节点,直到root节点,在遍历的过程中更新子节点的expirationTime
  var node = fiber.return; //fiber的父节点
  var root = null;

  if (node === null && fiber.tag === HostRoot) {
    root = fiber.stateNode;
  } else {
    while (node !== null) {
      alternate = node.alternate;

      if (node.childExpirationTime < expirationTime) {
        node.childExpirationTime = expirationTime;

        if (alternate !== null && alternate.childExpirationTime < expirationTime) {
          alternate.childExpirationTime = expirationTime;
        }
      } else if (alternate !== null && alternate.childExpirationTime < expirationTime) {
        alternate.childExpirationTime = expirationTime;
      }

      if (node.return === null && node.tag === HostRoot) {
        root = node.stateNode;
        break;
      }

      node = node.return;
    }
  }

  if (root !== null) {
    if (workInProgressRoot === root) {
      // Received an update to a tree that's in the middle of rendering. Mark
      // that's unprocessed work on this root.。
      // 当收到对渲染的中间树的更新,标记这是该根目录上的未处理工作。 
      markUnprocessedUpdateTime(expirationTime);

      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.
        // 当根目录已经被设置为延迟更新,这意味着该渲染一定不会结束。
        // 由于我们有新的更新,因此我们在标记新的 update 之前,将其标记为 
        // 现在暂停。这有中断当前渲染并切换到更新的效果。 
        // TODO: This happens to work when receiving an update during the render
        // phase, because of the trick inside computeExpirationForFiber to
        // subtract 1 from `renderExpirationTime` to move it into a
        // separate bucket. But we should probably model it with an exception,
        // using the same mechanism we use to force hydration of a subtree.
        // TODO: This does not account for low pri updates that were already
        // scheduled before the root started rendering. Need to track the next
        // pending expiration time (perhaps by backtracking the return path) and
        // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
        markRootSuspendedAtTime(root, renderExpirationTime);
      }
    } 
    // Mark that the root has a pending update.
    // 标记根已更新
    markRootUpdatedAtTime(root, expirationTime);
  }

  return root;
}

源码说明

fiber.return 指向 fiber 的父节点

  1. 更新 fiber 的过期时间,如果 fiber 上存在 alternate,同时更新 alternate 的过期时间;
  2. 根据 fiber.return 向上遍历父节点,直到找到 rootFiber(node === null && fiber.tag === HostRoot);在遍历的过程中更新父节点的 childExpirationTime,如果父节点上存在 alternate 同时更新 alternate.childExpirationTime;
  3. 找到 rootFiber 后,根据 rootFiber.stateNode=fiberRoot 的关系,找到 fiberRoot;
  4. markUnprocessedUpdateTime、markRootSuspendedAtTime 大致上看是用来做中断恢复的处理;
  5. 调用 markRootUpdatedAtTime 标记 fiberRoot 上有待处理的更新。

markRootUpdatedAtTime

// 标记根已更新
function markRootUpdatedAtTime(root, expirationTime) {
  // Update the range of pending times
  // 更新待处理时间范围 
  var firstPendingTime = root.firstPendingTime;

  if (expirationTime > firstPendingTime) {
    root.firstPendingTime = expirationTime;
  } 
  
  // Update the range of suspended times. Treat everything lower priority or
  // equal to this update as unsuspended.
  //更新暂停时间范围。对待所有低优先级的事物或等于此更新为未暂停。 
  var firstSuspendedTime = root.firstSuspendedTime;

  if (firstSuspendedTime !== NoWork) {
    if (expirationTime >= firstSuspendedTime) {
      // The entire suspended range is now unsuspended.
      root.firstSuspendedTime = root.lastSuspendedTime = root.nextKnownPendingLevel = NoWork;
    } else if (expirationTime >= root.lastSuspendedTime) {
      root.lastSuspendedTime = expirationTime + 1;
    } 
    // This is a pending level. Check if it's higher priority than the next
    // known pending level.
    // 这是一个待处理的级别。检查其优先级是否高于下一个已知的待处理级别。 
    if (expirationTime > root.nextKnownPendingLevel) {
      root.nextKnownPendingLevel = expirationTime;
    }
  }
}

源码说明

  • 更新 fiberRoot 的待处理的时间范围,即 firstPendingTime;
  • 并更新 fiberRoot 相关暂停的时间范围,即 firstSuspendedTime、lastSuspendedTime;
  • 跟下一个已知待处理的级别比较,如果当前 expirationTime 级别更高,更新 nextKnownPendingLevel;

markUnprocessedUpdateTime

// 标记未处理的更新时间
function markUnprocessedUpdateTime(expirationTime) {
  if (expirationTime > workInProgressRootNextUnprocessedUpdateTime) {
    workInProgressRootNextUnprocessedUpdateTime = expirationTime;
  }
}

// ??

markRootSuspendedAtTime

function markRootSuspendedAtTime(root, expirationTime) {
  var firstSuspendedTime = root.firstSuspendedTime;
  var lastSuspendedTime = root.lastSuspendedTime;

  if (firstSuspendedTime < expirationTime) {
    root.firstSuspendedTime = expirationTime;
  }

  if (lastSuspendedTime > expirationTime || firstSuspendedTime === NoWork) {
    root.lastSuspendedTime = expirationTime;
  }

  if (expirationTime <= root.lastPingedTime) {
    root.lastPingedTime = NoWork;
  }

  if (expirationTime <= root.lastExpiredTime) {
    root.lastExpiredTime = NoWork;
  }
}

// ??

checkForInterruption

// The root we're working on
var workInProgressRoot = null;
var enableUserTimingAPI = true;
var interruptedBy = null;

function checkForInterruption(fiberThatReceivedUpdate, updateExpirationTime) {
  if (enableUserTimingAPI && workInProgressRoot !== null && updateExpirationTime > renderExpirationTime) {
    interruptedBy = fiberThatReceivedUpdate;
  }
}

源码说明

判断是否有高优先级任务打断当前正在执行的任务

  • 如果当前 fiber 的优先级更高,需要打断当前执行的任务,立即执行该 fiber 上的 update,更新 interruptedBy 全局属性;
  • enableUserTimingAPI 的作用??

recordScheduleUpdate

// If we're in the middle of user code, which fiber and method is it?
// Reusing `currentFiber` would be confusing for this because user code fiber
// can change during commit phase too, but we don't need to unwind it (since
// lifecycles in the commit phase don't resemble a tree).

var currentPhase = null;
var currentPhaseFiber = null; 
// Did lifecycle hook schedule an update? This is often a performance problem,
// so we will keep track of it, and include it in the report.
// Track commits caused by cascading updates.
var isCommitting = false;
var hasScheduledUpdateInCurrentCommit = false;
var hasScheduledUpdateInCurrentPhase = false;
function recordScheduleUpdate() {
  if (enableUserTimingAPI) {
    if (isCommitting) {
      hasScheduledUpdateInCurrentCommit = true;
    }

    if (currentPhase !== null && currentPhase !== 'componentWillMount' && currentPhase !== 'componentWillReceiveProps') {
      hasScheduledUpdateInCurrentPhase = true;
    }
  }
}

源码说明

用来记录调度器的执行状态

schedulePendingInteractions

var enableSchedulerTracing = true; // SSR experiments
var tracing = require('scheduler/tracing');
function schedulePendingInteractions(root, expirationTime) {
  // This is called when work is scheduled on a root.
  // It associates the current interactions with the newly-scheduled expiration.
  // They will be restored when that expiration is later committed.
  // 当工作安排在根上时调用。 
  // 将当前交互与新计划的过期时间相关联。 
  // 它们将在以后到期时恢复。 

  if (!enableSchedulerTracing) {
    return;
  }

  scheduleInteractions(root, expirationTime, tracing.__interactionsRef.current);
}

源码说明

安排待处理的交互

  • 只是 scheduleInteractions 的加壳函数

scheduleInteractions

var DEFAULT_THREAD_ID = 0; 
// Counters used to generate unique IDs.
var interactionIDCounter = 0;
var threadIDCounter = 0;
function scheduleInteractions(root, expirationTime, interactions) {
  if (!enableSchedulerTracing) {
    return;
  }

  if (interactions.size > 0) {
    var pendingInteractionMap = root.pendingInteractionMap;
    var pendingInteractions = pendingInteractionMap.get(expirationTime);

    if (pendingInteractions != null) {
      interactions.forEach(function (interaction) {
        if (!pendingInteractions.has(interaction)) {
          // Update the pending async work count for previously unscheduled interaction.
          //更新以前未计划的交互的待处理异步工作计数。 
          interaction.__count++;
        }

        pendingInteractions.add(interaction);
      });
    } else {
      pendingInteractionMap.set(expirationTime, new Set(interactions)); 
      // Update the pending async work count for the current interactions.
      // 更新当前交互的待处理异步工作计数。 
      interactions.forEach(function (interaction) {
        interaction.__count++;
      });
    }

    var subscriber = tracing.__subscriberRef.current;

    if (subscriber !== null) {
      var threadID = computeThreadID(root, expirationTime);
      subscriber.onWorkScheduled(interactions, threadID);
    }
  }
}

源码说明

主要涉及到 scheduler-tracing 交互的处理,后面再详解。

computeThreadID

function computeThreadID(root, expirationTime) {
  // Interaction threads are unique per root and expiration time.
  return expirationTime * 1000 + root.interactionThreadID;
}

performSyncWorkOnRoot

// 函数调用栈:render > legacyRenderSubtreeIntoContainer > updateContainer >
// > scheduleWork > scheduleUpdateOnFiber > performSyncWorkOnRoot
// 传参:这里的 root 就是 markUpdateTimeFromFiberToRoot 返回的 fiberRoot
function performSyncWorkOnRoot(root) {
  // Check if there's expired work on this root. Otherwise, render at Sync.
  //检查此根目录上是否有过期的工作。否则,在同步时渲染。 
  var lastExpiredTime = root.lastExpiredTime;
  var expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;

  if (root.finishedExpirationTime === expirationTime) {
    // There's already a pending commit at this expiration time.
    // TODO: This is poorly factored. This case only exists for the
    // batch.commit() API.
    commitRoot(root);
  } else {
    if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
      {
        throw Error("Should not already be working.");
      }
    }

    flushPassiveEffects(); 
    // If the root or expiration time have changed, throw out the existing stack
    // and prepare a fresh one. Otherwise we'll continue where we left off.
    // 如果根或到期时间已更改,则丢弃现有堆栈并准备一个新的。
    // 否则,我们将从中断的地方继续。 
    if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
      prepareFreshStack(root, expirationTime);
      startWorkOnPendingInteractions(root, expirationTime);
    } 
    // If we have a work-in-progress fiber, it means there's still work to do
    // in this root.
    if (workInProgress !== null) {
      var prevExecutionContext = executionContext;
      executionContext |= RenderContext;
      var prevDispatcher = pushDispatcher(root);
      var prevInteractions = pushInteractions(root);
      startWorkLoopTimer(workInProgress);

      do {
        try {
          workLoopSync();
          break;
        } catch (thrownValue) {
          handleError(root, thrownValue);
        }
      } while (true);

      resetContextDependencies();
      executionContext = prevExecutionContext;
      popDispatcher(prevDispatcher);

      if (enableSchedulerTracing) {
        popInteractions(prevInteractions);
      }

      if (workInProgressRootExitStatus === RootFatalErrored) {
        var fatalError = workInProgressRootFatalError;
        stopInterruptedWorkLoopTimer();
        prepareFreshStack(root, expirationTime);
        markRootSuspendedAtTime(root, expirationTime);
        ensureRootIsScheduled(root);
        throw fatalError;
      }

      if (workInProgress !== null) {
        // This is a sync render, so we should have finished the whole tree.
        {
          {
            throw Error("Cannot commit an incomplete root. This error is likely caused by a bug in React. Please file an issue.");
          }
        }
      } else {
        // We now have a consistent tree. Because this is a sync render, we
        // will commit it even if something suspended.
        stopFinishedWorkLoopTimer();
        root.finishedWork = root.current.alternate;
        root.finishedExpirationTime = expirationTime;
        finishSyncRender(root, workInProgressRootExitStatus, expirationTime);
      } 
      // Before exiting, make sure there's a callback scheduled for the next
      // pending level.
      ensureRootIsScheduled(root);
    }
  }

  return null;
}

prepareFreshStack

// 准备新的堆栈
function prepareFreshStack(root, expirationTime) {
  root.finishedWork = null;
  root.finishedExpirationTime = NoWork;
  var timeoutHandle = root.timeoutHandle;

  if (timeoutHandle !== noTimeout) {
    // The root previous suspended and scheduled a timeout to commit a fallback
    // state. Now that we have additional work, cancel the timeout.
    root.timeoutHandle = noTimeout; 
    // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
    cancelTimeout(timeoutHandle);
  }

  if (workInProgress !== null) {
    var interruptedWork = workInProgress.return;

    while (interruptedWork !== null) {
      unwindInterruptedWork(interruptedWork);
      interruptedWork = interruptedWork.return;
    }
  }

  workInProgressRoot = root;
  workInProgress = createWorkInProgress(root.current, null, expirationTime);
  renderExpirationTime = expirationTime;
  workInProgressRootExitStatus = RootIncomplete;
  workInProgressRootFatalError = null;
  workInProgressRootLatestProcessedExpirationTime = Sync;
  workInProgressRootLatestSuspenseTimeout = Sync;
  workInProgressRootCanSuspendUsingConfig = null;
  workInProgressRootNextUnprocessedUpdateTime = NoWork;
  workInProgressRootHasPendingPing = false;

  if (enableSchedulerTracing) {
    spawnedWorkDuringRender = null;
  }

  {
    ReactStrictModeWarnings.discardPendingWarnings();
    componentsThatTriggeredHighPriSuspend = null;
  }
}

源码说明

  • 设置 workInProgress 相关的各个属性,关键是 workInProgressRoot、workInProgress(调用 createWorkInProgress 生成);

createWorkInProgress

// This is used to create an alternate fiber to do work on.
// 用于创建要处理的备用 fiber 
// 这里的 current 是 rootFiber
function createWorkInProgress(current, pendingProps, expirationTime) {
  var workInProgress = current.alternate;

  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we'll
    // only ever need at most two versions of a tree. We pool the "other" unused
    // node that we're free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    //我们使用双重缓冲池技术,因为我们知道最多只需要一棵树的两个版本。
    //我们将“其他”未使用的节点合并,这样我们就可以自由重用。
    //为了避免为永不更新的对象的对象分配额外的内存,它是懒创建的。
    //这也使我们能够在需要时,回收额外的内存。 
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    {
      // DEV-only fields
      workInProgress._debugID = current._debugID;
      workInProgress._debugSource = current._debugSource;
      workInProgress._debugOwner = current._debugOwner;
      workInProgress._debugHookTypes = current._debugHookTypes;
    }

    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps; 
    // We already have an alternate.
    // Reset the effect tag.
    workInProgress.effectTag = NoEffect; 
    // The effect list is no longer valid.
    workInProgress.nextEffect = null;
    workInProgress.firstEffect = null;
    workInProgress.lastEffect = null;

    if (enableProfilerTimer) {
      // We intentionally reset, rather than copy, actualDuration & actualStartTime.
      // This prevents time from endlessly accumulating in new commits.
      // This has the downside of resetting values for different priority renders,
      // But works for yielding (the common case) and should support resuming.
      workInProgress.actualDuration = 0;
      workInProgress.actualStartTime = -1;
    }
  }

  workInProgress.childExpirationTime = current.childExpirationTime;
  workInProgress.expirationTime = current.expirationTime;
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue; 
  // Clone the dependencies object. This is mutated during the render phase, 
  // so it cannot be shared with the current fiber.
  var currentDependencies = current.dependencies;
  workInProgress.dependencies = currentDependencies === null ? null : {
    expirationTime: currentDependencies.expirationTime,
    firstContext: currentDependencies.firstContext,
    responders: currentDependencies.responders
  }; 
  // These will be overridden during the parent's reconciliation
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;

  if (enableProfilerTimer) {
    workInProgress.selfBaseDuration = current.selfBaseDuration;
    workInProgress.treeBaseDuration = current.treeBaseDuration;
  }

  {
    workInProgress._debugNeedsRemount = current._debugNeedsRemount;

    switch (workInProgress.tag) {
      case IndeterminateComponent:
      case FunctionComponent:
      case SimpleMemoComponent:
        workInProgress.type = resolveFunctionForHotReloading(current.type);
        break;

      case ClassComponent:
        workInProgress.type = resolveClassForHotReloading(current.type);
        break;

      case ForwardRef:
        workInProgress.type = resolveForwardRefForHotReloading(current.type);
        break;

      default:
        break;
    }
  }

  return workInProgress;
}

源码说明

  • 如果 workInProgress 不存在,根据传入的 fiber 复制一份,并将各自的 alternate 属性指向对方;
  • 如果已存在,重置 effectTag 和相关的指针(nextEffect、firstEffect、lastEffect);
  • 避免 dependencies 属性的引用赋值,其他属性直接赋值;

startWorkOnPendingInteractions

// 开始处理待处理的交互
function startWorkOnPendingInteractions(root, expirationTime) {
  // This is called when new work is started on a root.
  if (!enableSchedulerTracing) {
    return;
  } 
  // Determine which interactions this batch of work currently includes, So that
  // we can accurately attribute time spent working on it, And so that cascading
  // work triggered during the render phase will be associated with it.
  var interactions = new Set();
  root.pendingInteractionMap.forEach(function (scheduledInteractions, scheduledExpirationTime) {
    if (scheduledExpirationTime >= expirationTime) {
      scheduledInteractions.forEach(function (interaction) {
        return interactions.add(interaction);
      });
    }
  }); 
  // Store the current set of interactions on the FiberRoot for a few reasons:
  // We can re-use it in hot functions like performConcurrentWorkOnRoot()
  // without having to recalculate it. We will also use it in commitWork() to
  // pass to any Profiler onRender() hooks. This also provides DevTools with a
  // way to access it when the onCommitRoot() hook is called.

  root.memoizedInteractions = interactions;

  if (interactions.size > 0) {
    var subscriber = tracing.__subscriberRef.current;

    if (subscriber !== null) {
      var threadID = computeThreadID(root, expirationTime);

      try {
        subscriber.onWorkStarted(interactions, threadID);
      } catch (error) {
        // If the subscriber throws, rethrow it in a separate task
        scheduleCallback(ImmediatePriority, function () {
          throw error;
        });
      }
    }
  }
}

源码说明

涉及 react 交互逻辑处理,后边再详解。

workLoopSync

function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

源码说明

循环调用 performUnitOfWork 处理 fiber 节点。

performUnitOfWork

function performUnitOfWork(unitOfWork) {
  // 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.
  // 该fiber的当前已刷新状态是作为备用。
  // 理想情况不应该依赖于此,但是在这里依赖它意味着我们
  // 在工作进行中的不需要再一个附加字段。
  var current?1 = unitOfWork.alternate;
  startWorkTimer(unitOfWork);
  setCurrentFiber(unitOfWork);
  var next;

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

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

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

  ReactCurrentOwner$2.current = null;
  return next;
}

源码说明

  • 处理 fiber 单元,核心就两个函数:beginWork?1、completeUnitOfWork,只为了拿到下一个需要处理的 fiber 节点。

这两个函数是调和阶段的核心,放到下一节详解。

flushSyncCallbackQueue

// Intentionally not named imports because Rollup would use dynamic dispatch for
// CommonJS interop named imports.
var Scheduler_runWithPriority = Scheduler.unstable_runWithPriority;
var Scheduler_scheduleCallback = Scheduler.unstable_scheduleCallback;
var Scheduler_cancelCallback = Scheduler.unstable_cancelCallback;
var Scheduler_shouldYield = Scheduler.unstable_shouldYield;
var Scheduler_requestPaint = Scheduler.unstable_requestPaint;
var Scheduler_now = Scheduler.unstable_now;
var Scheduler_getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
var Scheduler_ImmediatePriority = Scheduler.unstable_ImmediatePriority;
var Scheduler_UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
var Scheduler_NormalPriority = Scheduler.unstable_NormalPriority;
var Scheduler_LowPriority = Scheduler.unstable_LowPriority;
var Scheduler_IdlePriority = Scheduler.unstable_IdlePriority;

var immediateQueueCallbackNode = null;
function flushSyncCallbackQueue() {
  if (immediateQueueCallbackNode !== null) {
    var node = immediateQueueCallbackNode;
    immediateQueueCallbackNode = null;
    Scheduler_cancelCallback(node);
  }

  flushSyncCallbackQueueImpl();
}

flushSyncCallbackQueueImpl

//刷新同步回调队列实现
function flushSyncCallbackQueueImpl() {
  if (!isFlushingSyncQueue && syncQueue !== null) {
    // Prevent re-entrancy.
    isFlushingSyncQueue = true;
    var i = 0;

    try {
      var _isSync = true;
      var queue = syncQueue;
      runWithPriority$2(ImmediatePriority, function () {
        for (; i < queue.length; i++) {
          var callback = queue[i];

          do {
            callback = callback(_isSync);
          } while (callback !== null);
        }
      });
      syncQueue = null;
    } catch (error) {
      // If something throws, leave the remaining callbacks on the queue.
      if (syncQueue !== null) {
        syncQueue = syncQueue.slice(i + 1);
      } // Resume flushing in the next tick


      Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue);
      throw error;
    } finally {
      isFlushingSyncQueue = false;
    }
  }
}