本文仅仅是阅读 图解 React 原理系列 的笔记,了解更多内容请查看原文链接。

reconciler 运作流程的笔记。

概览

react-reconciler 主要的作用有 4 个方面:

  1. 输入:暴露 api 函数(如 scheduleUpdateOnFiber),供给其他包调用。
  2. 注册调度任务:与调度中心(scheduler)交互,注册调度任务 task,等待任务回调。
  3. 执行任务回调:在内存中构造出 fiber 树,同时与渲染器交互,在内存中创建出与 fiber 对应的 DOM 节点。
  4. 输出:与渲染器交互,渲染 DOM 节点。

主要功能代码集中于 react-reconciler/src/ReactFiberWorkLoop.new.js(v18.2.0),可以用图示表示:

输入

在 ReactFiberWorkLoop.js 中,承接输入函数的只有 scheduleUpdateOnFiber。在 react-reconciler 对外暴露的 api 函数中,只要涉及到需要改变 fiber 的操作,最后都会间接调用 scheduleUpdateOnFiber。

// 唯一接收输入信号的函数
export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number
) {
  if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) {
    //...
  } else {
    //...
    ensureRootIsScheduled(root, eventTime);
    //...
  }
}

注册调度任务

与输入环节紧密相连,scheduleUpdateOnFiber 函数之后,立即进入 ensureRootIsScheduled 函数。

// 该函数为 root 注册一个调度任务,在 root 下只能存在一个任务。
// 如果有任务已经调度,会检测确保现有的任务的优先级与 root 的下一个任务优先级相同。
// 该函数在每次更新和退出任务之前都会被调用。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // 前半部分:判断是否需要注册新的调度
  const existingCallbackNode = root.callbackNode;

  // 检测是否存在有通道(lanes)还在等待,如果有,将其标记为过期,以便接下来处理。
  markStarvedLanesAsExpired(root, currentTime);

  // 判断下个通道(lanes)的执行和优先级
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );

  if (nextLanes === NoLanes) {
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLanes;
    return;
  }

  // 使用最高的优先级通道代表回调的优先级
  const newCallbackPriority = getHighestPriorityLane(nextLanes);

  // 检查是否存在已有的任务
  const existingCallbackPriority = root.callbackPriority;
  if (existingCallbackPriority === newCallbackPriority) {
    return;
  }

  if (existingCallbackNode !== null) {
    cancelCallback(existingCallbackNode);
  }

  // 后半部分:注册一个新的调度任务
  let newCallbackNode;
  if (newCallbackPriority === SyncLane) {
    if (root.tag === LegacyRoot) {
      scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }
    if (supportsMicrotasks) {
      scheduleMicrotask(() => {
        flushSyncCallbacks();
      });
    } else {
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
    newCallbackNode = null;
  } else {
    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleEventPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root)
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

ensureRootIsScheduled 的逻辑分为 2 部分:

  1. 前半部分:判断是否需要注册新的调度(如果无需新的调度,退出函数)
  2. 后半部分:注册调度任务
    • performSyncWorkOnRoot 和 performConcurrentWorkOnRoot 都封装到了任务回调(scheduleCallback)中
    • 等待调度中心执行任务,任务其实就是执行 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot

执行任务回调

performSyncWorkOnRoot 的逻辑分为 3 部分:

  1. fiber 树构建
  2. 异常处理:有可能 fiber 树构建过程中出现异常
  3. 调用输出
// 不经过 scheduler 的同步任务入口
function performSyncWorkOnRoot(root) {
  flushPassiveEffects();

  // 1. fiber 树构建
  let lanes = getNextLanes(root, NoLanes);
  if (!includesSomeLane(lanes, SyncLane)) {
    ensureRootIsScheduled(root, now());
    return null;
  }

  let exitStatus = renderRootSync(root, lanes);

  // 2. 异常处理的:有可能 fiber 构建过程中实现异常
  if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
    //...
  }

  // 3. 渲染 Fiber 树
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);

  // 退出之前,确保下一个回调调度处于 pending 状态。
  ensureRootIsScheduled(root, now());
  return null;
}

performConcurrentWorkOnRoot 的逻辑与 performSyncWorkOnRoot 的不同之处在于,对于可中断渲染的支持:

  1. 调用 performConcurrentWorkOnRoot 函数时,首先检查是否处于渲染过程中,是否需要恢复上一次渲染。
  2. 如果本次渲染被中断,最后返回一个新的 performConcurrentWorkOnRoot 函数,等待下一次调用。
// 这是每一个并发任务的入口。
function performConcurrentWorkOnRoot(root) {
  // 1. 刷新 pending 状态的被动副作用,以防它们安排额外的工作。
  const originalCallbackNode = root.callbackNode;
  const didFlushPassiveEffects = flushPassiveEffects();
  if (didFlushPassiveEffects) {
    if (root.callbackNode !== originalCallbackNode) {
      return null;
    } else {
      // 当前任务不会被取消,继续
    }
  }

  // 2. 获取本次渲染的优先级
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );
  if (lanes === NoLanes) {
    return null;
  }

  // 构建 fiber 树
  let exitStatus = renderRootConcurrent(root, lanes);
  if (exitStatus !== RootInProgress) {
    if (exitStatus === RootErrored) {
      //...
    }
    if (exitStatus === RootFatalErrored) {
      //...
    }
    if (exitStatus === RootDidNotComplete) {
      markRootSuspended(root, lanes);
    } else {
      const renderWasConcurrent = !includesBlockingLane(root, lanes);
      const finishedWork: Fiber = (root.current.alternate: any);
      root.finishedWork = finishedWork;
      root.finishedLanes = lanes;
      // 渲染 fiber 树
      finishConcurrentRender(root, exitStatus, lanes);
    }
  }

  ensureRootIsScheduled(root, now());

  if (root.callbackNode === originalCallbackNode) {
    // 渲染被阻断,返回一个新的 performConcurrentWorkOnRoot 函数,等待下一次调用
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  return null;
}

输出

在输出阶段,commitRoot 的实现逻辑在 commitRootImpl 函数上,其主要逻辑是处理副作用队列,将最新的 fiber 树结构反映到 DOM 上。

function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<CapturedValue<mixed>>,
  transtions: Array<Transition> | null,
  renderPriorityLevel: EventPriority
) {
  // 设置局部变量
  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;

  // 清空 FiberRoot 对象上的属性
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  root.callbackNode = null;

  // 提交阶段
  let firstEffect = finishedWork.firstEffect;
  if (firstEffect !== null) {
    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;
    // 阶段1: dom突变之前
    nextEffect = firstEffect;
    do {
      commitBeforeMutationEffects();
    } while (nextEffect !== null);

    // 阶段2: dom突变, 界面发生改变
    nextEffect = firstEffect;
    do {
      commitMutationEffects(root, renderPriorityLevel);
    } while (nextEffect !== null);
    root.current = finishedWork;

    // 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等
    nextEffect = firstEffect;
    do {
      commitLayoutEffects(root, lanes);
    } while (nextEffect !== null);
    nextEffect = null;
    executionContext = prevExecutionContext;
  }
  ensureRootIsScheduled(root, now());
  return null;
}

核心逻辑分为 3 个步骤:

  1. commitBeforeMutationEffects

    • dom 变更之前,主要处理副作用队列中带有 Snapshot, Passive 标记的 fiber 节点。
  2. commitMutationEffects

    • dom 变更,界面得到更新,主要处理副作用队列中带有 Placement, Update, Deletion, Hydrating 标记的 fiber 节点。
  3. commitLayoutEffects

    • dom 变更后,主要处理副作用队列中带有 Update, Callback 标记的 fiber 节点。

参考链接