Reconciler是如何运作的

date
Feb 13, 2022
slug
react-reconciler
status
Published
tags
React
源码
summary
从源码解析Reconciler运作原理
type
Post
本文基于React17.0.0 legacy mode

主流程概况

在React中,reconciler的作用就像连接器,连接着reactscheduler
react-reconciler主要起到了如下作用:
  1. 向外提供入口:如scheduleUpdateOnFiberupdateContainer
  1. scheduler协作,根据优先级构建出task,等待scheduler来执行回调(scheduleSyncCallback)
  1. 构建fiber tree,与react-dom创造出DOM(renderRootSync)
  1. react-dom渲染DOM(commitRoot)

scheduleUpdateOnFiber

React是如何启动的中,scheduleUpdateOnFiberupdateContainer中被调度,进入reconciler流程。其实在关于fiber的操作中,无论是挂载或更新,scheduleUpdateOnFiber最终都会被调度。
进入scheduleUpdateOnFiber后有两种情况
  1. 本次为同步更新,且当前不在render阶段,直接调用performSyncWorkOnRoot执行同步任务
  1. 当前有更新任务在进行,由于无法打断,调用ensureRootIsScheduled,目的是复用在更新的任务,让这个已有的任务把本次更新给做了
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  // 第一步,检查是否有无限更新, 例如在render函数中调用了setState
  checkForNestedUpdates();
  // 第二步,向上收集fiber.childLanes
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) {
    return null;
  }
  // 第三步,在root上标记更新,将update的lane放到root.pendingLanes
  markRootUpdated(root, lane, eventTime);

  if (root === workInProgressRoot) {
    // workInProgressRoo存在,意味着是当前根节点触发的更新
    if (
      (executionContext & RenderContext) === NoContext
    ) {
      workInProgressRootUpdatedLanes = mergeLanes(
        workInProgressRootUpdatedLanes,
        lane,
      );
    }
    if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
      markRootSuspended(root, workInProgressRootRenderLanes);
    }
  }

  // 根据Scheduler的优先级获取到对应的React优先级
  const priorityLevel = getCurrentPriorityLevel();

  if (lane === SyncLane) { // 0b0000000000000000000000000000001
    // 根据Scheduler的优先级获取到对应的React优先级
    if (
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      // 判断是否不在render过程中
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // 如果是本次更新是同步的(lane === SyncLane),并且当前还未渲染,意味着主线程空闲,并没有React的
      // 更新任务在执行,那么调用performSyncWorkOnRoot开始执行同步任务
      schedulePendingInteractions(root, lane);
      performSyncWorkOnRoot(root);
    } else {
      // 如果当前有React更新任务正在进行,而且因为无法打断,所以调用ensureRootIsScheduled,
      // 目的是去复用已经在更新的任务,让这个已有的任务把这次更新顺便做了
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
      // 通过判断 executionContext 是否等于 NoContext 来确定当前更新流程是否在 React 事件流中
      // 如果不在(NoContext),直接调用 flushSyncCallbackQueue 更新
      if (executionContext === NoContext) {
        resetRenderTimer();
        flushSyncCallbackQueue();
      }
    }
  } else {
    // 异步操作
    if (
      // 是否是用户事件触发的上下文
      (executionContext & DiscreteEventContext) !== NoContext && // DiscreteEventContext = 0b0000100;

      // UserBlockingPriority = 98, ImmediatePriority = 99
      [UserBlockingSchedulerPriority, ImmediateSchedulerPriority].includes(priorityLevel)
    ) {

      if (rootsWithPendingDiscreteUpdates === null) {
        rootsWithPendingDiscreteUpdates = new Set([root]);
      } else {
        rootsWithPendingDiscreteUpdates.add(root);
      }
    }

    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);
  }


  mostRecentlyUpdatedRoot = root;
}

ensureRootIsScheduled

接下来看看ensureRootIsScheduled
  1. 前半部分判断了是否需要重新注册新的调度,如果不需要则会return
  1. 走到后半部分,表示需要注册新的调度,则需要判断当前mode,来将performSyncWorkOnRootperformConcurrentWorkOnRoot作为回调传入scheduleCallback中,等待scheduler执行
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {

  // 获取旧任务,对应task上的callback,代表当前根节点正在被调度的任务
  const existingCallbackNode = root.callbackNode;

  // 记录任务的过期时间,检查是否有过期任务,有则立即将它放到root.expiredLanes,
  // 便于接下来将这个任务以同步模式立即调度
  markStarvedLanesAsExpired(root, currentTime);
  // 获取renderLanes,顺便计算return_highestLanePriority,也即是下面的newCallbackPriority
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  const newCallbackPriority = returnNextLanesPriority();

  if (nextLanes === NoLanes) {
    // 如果渲染优先级为空,则不需要调度
    if (existingCallbackNode !== null) {
      // 如果存在旧任务那么就取消掉
      // 即existingCallbackNode.callback = null
      cancelCallback(existingCallbackNode);
      // 然后root上相应置空
      root.callbackNode = null;
      root.callbackPriority = NoLanePriority;
    }
    return;
  }
  // 如果存在旧任务,那么看一下能否复用
  if (existingCallbackNode !== null) {
    // 获取旧任务的优先级
    const existingCallbackPriority = root.callbackPriority;
    // 如果新旧任务的优先级相同,则无需调度,如多次调用setState:
    // onClick = () => {
    // setState(1);
    // setState(2);
    //}
    if (existingCallbackPriority === newCallbackPriority) {
      return;
    }
    // 代码执行到这里说明新任务的优先级高于旧任务的优先级,取消掉旧任务,实现高优先级任务插队
    cancelCallback(existingCallbackNode);
  }
  // 调度一个新任务
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    // 若新任务的优先级(newCallbackPriority)为同步优先级(SyncLanePriority),则同步调度
    // 传统的同步渲染和过期任务会走这里
    // 同步渲染模式
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    // 同步模式到concurrent模式的过渡模式:blocking模式会走这里
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    // 否则就是concurrent模式
    // 将本次更新任务的优先级转化为调度优先级
    // schedulerPriorityLevel为调度优先级
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    // concurrent模式
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
  // 更新root上的任务优先级和任务,以便下次发起调度时候可以获取到
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

performSyncWorkOnRoot

performSyncWorkOnRoot
  1. 会先做一些前置处理:清除上一次更新未完成的effects、处理优先级相关、处理hydrate相关等
  1. 调用renderRootSync,构建fiber tree(render阶段)
  1. 调用commitRoot,渲染DOM(commit阶段)
function performSyncWorkOnRoot(root) {
  invariant(
    (executionContext & (RenderContext | CommitContext)) === NoContext,
    'Should not already be working.',
  );


  // 清除未执行完的effects
  flushPassiveEffects();

  // 优先级相关
  let lanes;
  let exitStatus;
  if (
    root === workInProgressRoot &&
    includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
  ) {
    lanes = workInProgressRootRenderLanes;
    // 构造fiber tree
    exitStatus = renderRootSync(root, lanes);
    if (
      includesSomeLane(
        workInProgressRootIncludedLanes,
        workInProgressRootUpdatedLanes,
      )
    ) {
      lanes = getNextLanes(root, lanes);
      exitStatus = renderRootSync(root, lanes);
    }
  } else {
    lanes = getNextLanes(root, NoLanes);
    // 构造fiber tree
    exitStatus = renderRootSync(root, lanes);
  }

  if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
    executionContext |= RetryAfterError;

    if (root.hydrate) {
      root.hydrate = false;
      clearContainer(root.containerInfo);
    }

    lanes = getLanesToRetrySynchronouslyOnError(root);
    if (lanes !== NoLanes) {
      exitStatus = renderRootSync(root, lanes);
    }
  }

  if (exitStatus === RootFatalErrored) {
    const fatalError = workInProgressRootFatalError;
    prepareFreshStack(root, NoLanes);
    markRootSuspended(root, lanes);
    ensureRootIsScheduled(root, now());
    throw fatalError;
  }

  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  // 渲染DOM
  commitRoot(root);
	// 结束前再检查下是否有新的更新需要重新发起调度
  ensureRootIsScheduled(root, now());

  return null;
}

renderRootSync

renderRootSync通过一个死循环调用workLoopSync
function renderRootSync(root: FiberRoot, lanes: Lanes) {

  const prevExecutionContext = executionContext;
  executionContext |= RenderContext;
  const prevDispatcher = pushDispatcher();

  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes);
    startWorkOnPendingInteractions(root, lanes);
  }

  const prevInteractions = pushInteractions(root);

  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  resetContextDependencies();
  if (enableSchedulerTracing) {
    popInteractions(((prevInteractions: any): Set<Interaction>));
  }

  executionContext = prevExecutionContext;
  popDispatcher(prevDispatcher);

  if (workInProgress !== null) {
    // This is a sync render, so we should have finished the whole tree.
    invariant(
      false,
      'Cannot commit an incomplete root. This error is likely caused by a ' +
        'bug in React. Please file an issue.',
    );
  }


  workInProgressRoot = null;
  workInProgressRootRenderLanes = NoLanes;

  return workInProgressRootExitStatus;
}

commitRoot

render阶段结束后,进入commit阶段,内部又分为三个阶段
  1. before mutaion(执行DOM操作前)处理effectListSnapShotPassive tag的fiber
  1. mutaion(执行DOM操作)处理PlacementUpdateDeletionHydrating tag的fiber
  1. layout(执行DOM操作后)处理UpdateCallback tag的fiber
至此,reconciler的主要流程就结束了。

render与commit核心原理

  • 更新中

© kaba 2019 - 2022