React Fiber 架构原理之2 —— 自底向上盘一盘 Scheduler

本文详述React Fiber架构中的调度器原理,探讨Scheduler如何实现分层调度、任务管理和优先级分配,以及如何利用浏览器空闲时间进行任务执行。通过自底向上的分析,从SchedulerHostConfig的空闲回调实现,到Scheduler的调度策略,再到React如何利用Scheduler实现可中断更新,揭示了React Fiber在性能优化上的核心机制。
摘要由CSDN通过智能技术生成

这篇讨论 Fiber 架构调度部分的实现原理,你将看到:

  • Fiber 架构的调度能力的分层设计。
  • Scheduler 的分片原理,以及调度器如何基于浏览器能力实现“空闲回调”和“时间管理”。
  • Scheduler 中的任务是怎样注册管理、派发执行、定义优先级的。
  • React 如何利用 Scheduler 实现“可中断更新”。

Part 0 背景

Fiber 架构是 React 一次伟大的革新。

React Fiber 是 React 核心算法的重新实现。 它的主要特点是渐进式渲染: 能够将渲染工作分割成块,并将其分散到多个帧。 其他关键特性包括在新的更新到来时暂停、中止或重用工作的能力; 为不同类型的更新分配优先级的能力; 以及新的并发方式。 ——GitHub - acdlite/react-fiber-architecture: A description of React’s new core algorithm, React Fiber

为了实现上述特性,Fiber 架构加入了关键的 Scheduler(调度器)。有了 Scheduler 这个“大脑”,React 在 setState 后不再直接启动“协调”过程,而是把本次更新注册到 Scheduler,再由 Scheduler 根据浏览器剩余空闲时间、优先级等因素派发给 Reconciler(协调器),并通过中断查询控制协调的中断重启。(协调就是我们说的包含 Diffing 的虚拟 DOM 构建计算过程,参考上篇)

编辑切换为全宽

添加图片注释,不超过 140 字(可选)

所以这样的 Scheduler 要支持哪些能力呢?

1.要能维护一个“任务池”
2.要提供一系列“优先级”的定义,并派发高优任务
3.要能感知浏览器的空闲,并根据剩余时间,随时给出“能不能继续工作”的建议

而 Reconciler 也要通过对 Scheduler 能力的调用,管理协调过程的发起、暂停、终止。

Part 1 调度的分层实现和调用链路

为了进一步看清 Scheduler 在 React 中的角色,这里有一张图解释整个调度的分层实现和调用链路。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

React 源码是分包组织的,packages 下面有若干个相对独立的包,react-reconciler、scheduler 就是其中两个,包下面是具体文件模块,这里列举了四个关键的。

  • 当我们调用 setState 之类的 api,组件会把状态入队后调 ReactFiberScheduler 注册本次修改。
  • ReactFiberScheduler,主要做 Fiber 调度、协调管理这些事。比如注册调度并提供回调函数发起协调、管理当前协调的节点。这一层并不直接依赖 Scheduler 的 API(可能觉得不优雅?),而是 react-reconciler 内部的一个封装模块。
  • SchedulerWithReactIntegration,就是那个封装模块,直接调 Scheduler,并把API改了改名字透传出来。
  • Scheduler,是调度器最核心的实现,实现了Part0 中说的第1、2点能力。这里做了优先级定义、任务池维护/注册/取消、任务调度执行、中断判断。这些能力又依赖对宿主时间片的判断,什么时候空闲、剩多少时间。在早些版本的 Fiber 中宿主时间片也做在 Scheduler,后来拆出去了。
  • SchedulerHostConfig,实现了宿主时间片部分,也就是 Part0 的第 3 点能力。

接下来我们按依赖顺序,自底向上,看看各层具体的实现方式。

Part 2 SchedulerHostConfig 宿主时间片判断

SchedulerHostConfig 要基于宿主(这里只谈浏览器)API,实现时间片管理。它要回答两个问题:

1.浏览器什么时候有空?空了叫我
2.此时此刻,我要不要让出线程给浏览器?

为什么要回答这些问题,怎么回答这些问题,就要从浏览器机制说起。

单线程JS 和浏览器帧

众所周知,浏览器里 JS 是单线程的。不但 JS 执行本身单线程,而且 JS 执行引擎和浏览器渲染引擎都挤在单个线程里。

好在大部分情况下,JS 执行、浏览器渲染都足够快,所以浏览器只要在单位时间内交替执行 JS 引擎和渲染引擎就好。这个单位时间叫做“帧(frame)”,目前主流的是60fps,每秒60轮,快到用户肉眼根本看不出来交替执行。一帧的生命周期如下:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

\

但不排除某一帧下,触发了一个巨复杂的 js 逻辑,把当前帧事件耗完了还没跑完,甚至跨了几个帧都没跑完。那浏览器就拿他没办法,必须等他跑完才能做渲染,这样用户就发现:“哎,刚刚有段时间页面卡住不动了”。巧的是,一个很庞大的虚拟DOM树的 Diffing 就可能是这种卡帧的逻辑,所以必须在需要的时候“暂时”退出来,让浏览器先把这帧的渲染跑了,回头再继续跑。

再回到大多数情况。当 js 不那么复杂时,这一帧的 js 和渲染跑完后,是有剩余时间的。这时候浏览器就会通过某种方式通知出来。让我们知道:“浏览器现在有空闲了”。

浏览器的空闲回调

这时候大名鼎鼎的 requestIdleCallback 出场了。

window.requestIdleCallback()方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。 —— requestIdleCallback - Web API 接口参考 | MDN

看起来完美对不对,但它的兼容性堪忧:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

\

所以 React 找了个替代品 —— MessageChannel。

Channel Messaging API 的MessageChannel 接口允许我们创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据。 —— MessageChannel - Web API 接口参考 | MDN

这是个 Full Support 的 API。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值