React18源码系列(一):React设计理念与架构

347 阅读4分钟

1、 I/O瓶颈和CPU瓶颈·

在React官网中,“React哲学”这一节中关于React理念的描述是:

我们认为,React是用JavaScript构建快速响应的大型Web应用程序的首选方式。

这意味着React是一款“重运行时”的应用级框架,它更偏向“运行时”的特性。关键在于“快速响应”,通常有以下两种场景会制约“快速响应”:

  • 当执行大型计算量的操作或者设备性能不足时,页面掉帧,导致卡顿,这类场景概括为CPU瓶颈;
  • 进行I/O操作(如发送网络请求)后,需要等待数据返回才能继续操作,导致不能快速响应,这类场景概括为I/O瓶颈。

浏览器渲染

  1. HTML解析为DOM树
  2. 解析CSS
  3. Layout:构建布局
  4. 划分图层 Layer
  5. 绘制 Paint

上述过程又被称为浏览器的渲染流水线

屏幕刷新频率通常是60Hz,即每秒刷新60次,每16.6ms刷新一次。在这16.6ms要做Js脚本执行,布局构建,样式绘制等很多事情;众所周知,JS脚本执行和渲染流水线是互斥的,当JS长时间执行时,就会造成页面掉帧,导致卡顿。

所以如何解决这个问题呢?React给出的方案就是异步可中断, 将一个长时间任务切分成任务单元异步执行,任务按照优先级划分,高优先执行,所以不管是解决CPU瓶颈还是解决I/O瓶颈,底层的诉求都是:实现Time Slice(时间切片)

2、React的新老架构

React在v15之后升级到v16,重构了整个架构,重构的主要原因就是因为老的架构无法实现Time Slice。

2.1 React v15架构

React v15架构可以分为两部分

  1. Reconciler(协调器)—— VDOM的实现,负责根据状态改变计算出UI变化。
  2. Renderer(渲染器)—— 接收到Reconciler的通知,负责将变化的UI渲染到页面上。

更新流程:

  • 调用组件的render方法,将返回的JSX转化为VDOM
  • 将VDOM与上次更新的VDOM进行对比,找出本次更新中变化的VDOM
  • Reconciler完成通知Renderer,将更新渲染到页面上

缺点:

更新流程一旦开始,中途无法中断,因为此时采用递归去遍历节点,如果层级很深,就会造成卡顿现象,也是基于这个原因,React16重构了架构。

2.2 React v16及以后架构

React v16及以后架构可分为三个部分

  1. Scheduler(调度器)—— 调度任务的优先级,高优先级任务优先进入Reconciler;
  2. Reconciler(协调器)—— VDOM的实现,负责根据状态改变计算出UI变化
  3. Renderer(渲染器)—— 负责将变化的UI渲染到页面上

React v15 采用了递归的方式进行VDOM对比,也是由此导致更新一旦开始,就无法中断,如果VDOM Tree层级较深,VDOM对比就长时间占据主线层,无法执行其他任务,造成页面卡顿现象;在新的架构中,Reconciler中的更新流程从递归变成了“可中断的循环过程”,也就是采用循环模拟递归,VDOM的对比过程放在了浏览器空闲时间,不会长期占据主线程,从而解决VDOM对比造成的页面卡顿的问题;

在每次循环中,都会调用shouldYild() 方法判断当前Time Slice是否还有剩余时间,没有剩余时间则暂停更新流程,将主线程交还给渲染流水线,等待下一个宏任务再继续执行,这也是Time Slice的实现原理

function shouldYield(){
  // 当前时间是否大于过期时间
  // 其中deadline = getCurrentTime() + yieldInterval
  // yieldInterval为调度器Scheduler预设的时间间隔,为5ms
  return getCurrentTime() >= deadline
}

当Scheduler将调度后的任务交给Reconciler后,Reconciler最终会为VDOM元素标记各种副作用flags,比如

// 代表插入或移动元素
export const placement = 0b00000000000000000000000010
// 代表更新元素
export const Update = 0b00000000000000000000000100
// 代表删除元素
export const Deletion = 0b00000000000000000000001000

Scheduler 和 Reconciler的工作都在内存中进行,只有当Reconciler完成工作后,工作流程才会进入Renderer,也就是react的commit阶段,然后Renderer根据VDOM元素标记的各种flags执行对应的操作。