title: reconciler 运作流程

reconciler 运作流程

概览

通过前文宏观包结构两大工作循环中的介绍, 对react-reconciler包有一定了解.

此处先归纳一下react-reconciler包的主要作用, 将主要功能分为 4 个方面:

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

以上功能源码都集中在ReactFiberWorkLoop.js中. 现在将这些功能(从输入到输出)串联起来, 用下图表示:

reconciler 运作流程 - 图1

图中的1,2,3,4步骤可以反映react-reconciler从输入到输出的运作流程,这是一个固定流程, 每一次更新都会运行.

分解

图中只列举了最核心的函数调用关系(其中的每一步都有各自的实现细节, 会在后续的章节中逐一展开). 将上述 4 个步骤逐一分解, 了解它们的主要逻辑.

输入

ReactFiberWorkLoop.js中, 承接输入的函数只有scheduleUpdateOnFiber源码地址. 在react-reconciler对外暴露的 api 函数中, 只要涉及到需要改变 fiber 的操作(无论是首次渲染后续更新操作), 最后都会间接调用scheduleUpdateOnFiber, 所以scheduleUpdateOnFiber函数是输入链路中的必经之路.

  1. // 唯一接收输入信号的函数
  2. export function scheduleUpdateOnFiber(
  3. fiber: Fiber,
  4. lane: Lane,
  5. eventTime: number,
  6. ) {
  7. // ... 省略部分无关代码
  8. const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  9. if (lane === SyncLane) {
  10. if (
  11. (executionContext & LegacyUnbatchedContext) !== NoContext &&
  12. (executionContext & (RenderContext | CommitContext)) === NoContext
  13. ) {
  14. // 直接进行`fiber构造`
  15. performSyncWorkOnRoot(root);
  16. } else {
  17. // 注册调度任务, 经过`Scheduler`包的调度, 间接进行`fiber构造`
  18. ensureRootIsScheduled(root, eventTime);
  19. }
  20. } else {
  21. // 注册调度任务, 经过`Scheduler`包的调度, 间接进行`fiber构造`
  22. ensureRootIsScheduled(root, eventTime);
  23. }
  24. }

逻辑进入到scheduleUpdateOnFiber之后, 后面有 2 种可能:

  1. 不经过调度, 直接进行fiber构造.
  2. 注册调度任务, 经过Scheduler包的调度, 间接进行fiber构造.

注册调度任务

输入环节紧密相连, scheduleUpdateOnFiber函数之后, 立即进入ensureRootIsScheduled函数(源码地址):

  1. // ... 省略部分无关代码
  2. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  3. // 前半部分: 判断是否需要注册新的调度
  4. const existingCallbackNode = root.callbackNode;
  5. const nextLanes = getNextLanes(
  6. root,
  7. root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  8. );
  9. const newCallbackPriority = returnNextLanesPriority();
  10. if (nextLanes === NoLanes) {
  11. return;
  12. }
  13. if (existingCallbackNode !== null) {
  14. const existingCallbackPriority = root.callbackPriority;
  15. if (existingCallbackPriority === newCallbackPriority) {
  16. return;
  17. }
  18. cancelCallback(existingCallbackNode);
  19. }
  20. // 后半部分: 注册调度任务
  21. let newCallbackNode;
  22. if (newCallbackPriority === SyncLanePriority) {
  23. newCallbackNode = scheduleSyncCallback(
  24. performSyncWorkOnRoot.bind(null, root),
  25. );
  26. } else if (newCallbackPriority === SyncBatchedLanePriority) {
  27. newCallbackNode = scheduleCallback(
  28. ImmediateSchedulerPriority,
  29. performSyncWorkOnRoot.bind(null, root),
  30. );
  31. } else {
  32. const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
  33. newCallbackPriority,
  34. );
  35. newCallbackNode = scheduleCallback(
  36. schedulerPriorityLevel,
  37. performConcurrentWorkOnRoot.bind(null, root),
  38. );
  39. }
  40. root.callbackPriority = newCallbackPriority;
  41. root.callbackNode = newCallbackNode;
  42. }

ensureRootIsScheduled的逻辑很清晰, 分为 2 部分:

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

执行任务回调

任务回调, 实际上就是执行performSyncWorkOnRootperformConcurrentWorkOnRoot. 简单看一下它们的源码(在fiber树构造章节再深入分析), 将主要逻辑剥离出来, 单个函数的代码量并不多.

performSyncWorkOnRoot:

  1. // ... 省略部分无关代码
  2. function performSyncWorkOnRoot(root) {
  3. let lanes;
  4. let exitStatus;
  5. lanes = getNextLanes(root, NoLanes);
  6. // 1. fiber树构造
  7. exitStatus = renderRootSync(root, lanes);
  8. // 2. 异常处理: 有可能fiber构造过程中出现异常
  9. if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
  10. // ...
  11. }
  12. // 3. 输出: 渲染fiber树
  13. const finishedWork: Fiber = (root.current.alternate: any);
  14. root.finishedWork = finishedWork;
  15. root.finishedLanes = lanes;
  16. commitRoot(root);
  17. // 退出前再次检测, 是否还有其他更新, 是否需要发起新调度
  18. ensureRootIsScheduled(root, now());
  19. return null;
  20. }

performSyncWorkOnRoot的逻辑很清晰, 分为 3 部分:

  1. fiber 树构造
  2. 异常处理: 有可能 fiber 构造过程中出现异常
  3. 调用输出

performConcurrentWorkOnRoot

  1. // ... 省略部分无关代码
  2. function performConcurrentWorkOnRoot(root) {
  3. const originalCallbackNode = root.callbackNode;
  4. // 1. 刷新pending状态的effects, 有可能某些effect会取消本次任务
  5. const didFlushPassiveEffects = flushPassiveEffects();
  6. if (didFlushPassiveEffects) {
  7. if (root.callbackNode !== originalCallbackNode) {
  8. // 任务被取消, 退出调用
  9. return null;
  10. } else {
  11. // Current task was not canceled. Continue.
  12. }
  13. }
  14. // 2. 获取本次渲染的优先级
  15. let lanes = getNextLanes(
  16. root,
  17. root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  18. );
  19. // 3. 构造fiber树
  20. let exitStatus = renderRootConcurrent(root, lanes);
  21. if (
  22. includesSomeLane(
  23. workInProgressRootIncludedLanes,
  24. workInProgressRootUpdatedLanes,
  25. )
  26. ) {
  27. // 如果在render过程中产生了新的update, 且新update的优先级与最初render的优先级有交集
  28. // 那么最初render无效, 丢弃最初render的结果, 等待下一次调度
  29. prepareFreshStack(root, NoLanes);
  30. } else if (exitStatus !== RootIncomplete) {
  31. // 4. 异常处理: 有可能fiber构造过程中出现异常
  32. if (exitStatus === RootErrored) {
  33. // ...
  34. }.
  35. const finishedWork: Fiber = (root.current.alternate: any);
  36. root.finishedWork = finishedWork;
  37. root.finishedLanes = lanes;
  38. // 5. 输出: 渲染fiber树
  39. finishConcurrentRender(root, exitStatus, lanes);
  40. }
  41. // 退出前再次检测, 是否还有其他更新, 是否需要发起新调度
  42. ensureRootIsScheduled(root, now());
  43. if (root.callbackNode === originalCallbackNode) {
  44. // 渲染被阻断, 返回一个新的performConcurrentWorkOnRoot函数, 等待下一次调用
  45. return performConcurrentWorkOnRoot.bind(null, root);
  46. }
  47. return null;
  48. }

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

  1. 调用performConcurrentWorkOnRoot函数时, 首先检查是否处于render过程中, 是否需要恢复上一次渲染.
  2. 如果本次渲染被中断, 最后返回一个新的 performConcurrentWorkOnRoot 函数, 等待下一次调用.

输出

commitRoot:

  1. // ... 省略部分无关代码
  2. function commitRootImpl(root, renderPriorityLevel) {
  3. // 设置局部变量
  4. const finishedWork = root.finishedWork;
  5. const lanes = root.finishedLanes;
  6. // 清空FiberRoot对象上的属性
  7. root.finishedWork = null;
  8. root.finishedLanes = NoLanes;
  9. root.callbackNode = null;
  10. // 提交阶段
  11. let firstEffect = finishedWork.firstEffect;
  12. if (firstEffect !== null) {
  13. const prevExecutionContext = executionContext;
  14. executionContext |= CommitContext;
  15. // 阶段1: dom突变之前
  16. nextEffect = firstEffect;
  17. do {
  18. commitBeforeMutationEffects();
  19. } while (nextEffect !== null);
  20. // 阶段2: dom突变, 界面发生改变
  21. nextEffect = firstEffect;
  22. do {
  23. commitMutationEffects(root, renderPriorityLevel);
  24. } while (nextEffect !== null);
  25. root.current = finishedWork;
  26. // 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等
  27. nextEffect = firstEffect;
  28. do {
  29. commitLayoutEffects(root, lanes);
  30. } while (nextEffect !== null);
  31. nextEffect = null;
  32. executionContext = prevExecutionContext;
  33. }
  34. ensureRootIsScheduled(root, now());
  35. return null;
  36. }

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

核心逻辑分为 3 个步骤:

  1. commitBeforeMutationEffects
    • dom 变更之前, 主要处理副作用队列中带有Snapshot,Passive标记的fiber节点.
  2. commitMutationEffects
    • dom 变更, 界面得到更新. 主要处理副作用队列中带有Placement, Update, Deletion, Hydrating标记的fiber节点.
  3. commitLayoutEffects
    • dom 变更后, 主要处理副作用队列中带有Update | Callback标记的fiber节点.

总结

本节从宏观上分析了reconciler 运作流程, 并将其分为了 4 个步骤, 基本覆盖了react-reconciler包的核心逻辑.