图解React源码 - React 工作循环 (workLoop)

2,090 阅读4分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

本文基于当前最新稳定版v17.0.2

在前文(React 应用的宏观包结构)中, 介绍了react核心包之间的依赖和调用关系, 并绘制出了概览图. 在概览图中, 可以看到有两个大的循环, 它们分别位于schedulerreact-reconciler包中:

workloop.png

本文将这两个循环分别表述为任务调度循环fiber构造循环. 接下来从宏观角度阐述这两大循环的作用, 以及它们之间的区别和联系. 更深入的源码分析分别在scheduler 调度机制fiber 树构造章节中详细解读.

  1. 任务调度循环

源码位于Scheduler.js, 它是react应用得以运行的保证, 它需要循环调用, 控制所有任务(task)的调度.

  1. fiber构造循环

源码位于ReactFiberWorkLoop.js, 控制 fiber 树的构造, 整个过程是一个深度优先遍历.

这两个循环对应的 js 源码不同于其他闭包(运行时就是闭包), 其中定义的全局变量, 不仅是该作用域的私有变量, 更用于控制react应用的执行过程.

区别于联系

  1. 区别

    • 任务调度循环是以二叉堆为数据结构(详见react 算法之堆排序), 循环执行的顶点, 直到被清空.
    • 任务调度循环的逻辑偏向宏观, 它调度的是每一个任务(task), 而不关心这个任务具体是干什么的(甚至可以将Scheduler包脱离react使用), 具体任务其实就是执行回调函数performSyncWorkOnRootperformConcurrentWorkOnRoot.
    • fiber构造循环是以为数据结构, 从上至下执行深度优先遍历(详见react 算法之深度优先遍历).
    • fiber构造循环的逻辑偏向具体实现, 它是只是任务(task)的一部分(如performSyncWorkOnRoot包括: fiber树的构造, DOM渲染, 调度检测), 只负责的是fiber树的构造.
  2. 联系

    • fiber构造循环任务调度循环中的任务(task)的一部分. 它们是从属关系, 每个任务都会重新构造一个fiber树.

主干逻辑

通过上文的描述, 两大循环的分工可以总结为: 大循环(任务调度循环)负责调度task, 小循环(fiber 构造循环)负责实现task .

react 运行的主干逻辑, 即将输入转换为输出的核心步骤, 实际上就是围绕这两大工作循环进行展开.

结合上文的宏观概览图(展示核心包之间的调用关系), 可以将 react 运行的主干逻辑进行概括:

  1. 输入: 将每一次更新(如: 新增, 删除, 修改节点之后)视为一次更新需求(目的是要更新DOM节点).
  2. 注册调度任务: react-reconciler收到更新需求之后, 并不会立即构造fiber树, 而是去调度中心scheduler注册一个新任务task, 即把更新需求转换成一个task.
  3. 执行调度任务(输出): 调度中心scheduler通过任务调度循环来执行task(task的执行过程又回到了react-reconciler包中).
    • fiber构造循环task的实现环节之一, 循环完成之后会构造出最新的 fiber 树.
    • commitRoottask的实现环节之二, 把最新的 fiber 树最终渲染到页面上, task完成.

主干逻辑就是输入到输出这一条链路, 为了更好的性能(如批量更新, 可中断渲染等功能), react在输入到输出的链路上做了很多优化策略, 比如本文讲述的任务调度循环fiber构造循环相互配合就可以实现可中断渲染.

总结

本节从宏观角度描述了react源码中的两大工作循环. 通过这两个大循环概括出react运行的主干逻辑. react-reconcilerScheduler包代码量多且逻辑复杂, 但实际上大部分都是服务于这个主干. 了解这两大循环, 更容易理解react的整体运行链路.

写在最后

本文属于图解react源码系列中的基本概念板块, 本系列近 20 篇文章, 不是为了面试, 真的是为了搞懂React源码, 进而提升架构和编码能力.

目前图解部分初稿已经全部完成, 修正之后将在8月全部更新, 如文章有表述错误, 会在github第一时间修正.