• 【React 源码】(四)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中. 现在将这些功能(从输入到输出)串联起来, 用下图表示:

    图中的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 变更, 界面得到更新. 主要处理副作用队列中带有PlacementUpdateDeletionHydrating标记的fiber节点.
    3. commitLayoutEffects
      • dom 变更后, 主要处理副作用队列中带有Update | Callback标记的fiber节点.

    总结

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

  • 相关阅读:
    【NLP 系列】Bert 词向量的空间分布
    鸿蒙开发之ArkTS 基础九 枚举类型
    Linux 日志系统API详解
    【案例教学】华为云API对话机器人的魅力—体验AI垃圾分类机器人
    Java发送mail并更新日历信息及jar包冲突问题
    400Gbps 网络面临的挑战
    find 命令这 7 种高级用法,你知道多少
    Matlab:在命令窗口中输入语句
    seccon 2022 quals wp
    UniApp集成微信小程序原生分包
  • 原文地址:https://blog.csdn.net/weixin_44828588/article/details/126467508