1、 I/O瓶颈和CPU瓶颈·
在React官网中,“React哲学”这一节中关于React理念的描述是:
我们认为,React是用JavaScript构建快速响应的大型Web应用程序的首选方式。
这意味着React是一款“重运行时”的应用级框架,它更偏向“运行时”的特性。关键在于“快速响应”,通常有以下两种场景会制约“快速响应”:
- 当执行大型计算量的操作或者设备性能不足时,页面掉帧,导致卡顿,这类场景概括为CPU瓶颈;
- 进行I/O操作(如发送网络请求)后,需要等待数据返回才能继续操作,导致不能快速响应,这类场景概括为I/O瓶颈。
浏览器渲染
- HTML解析为DOM树
- 解析CSS
- Layout:构建布局
- 划分图层 Layer
- 绘制 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架构可以分为两部分
- Reconciler(协调器)—— VDOM的实现,负责根据状态改变计算出UI变化。
- Renderer(渲染器)—— 接收到Reconciler的通知,负责将变化的UI渲染到页面上。
更新流程:
- 调用组件的render方法,将返回的JSX转化为VDOM
- 将VDOM与上次更新的VDOM进行对比,找出本次更新中变化的VDOM
- Reconciler完成通知Renderer,将更新渲染到页面上
缺点:
更新流程一旦开始,中途无法中断,因为此时采用递归去遍历节点,如果层级很深,就会造成卡顿现象,也是基于这个原因,React16重构了架构。
2.2 React v16及以后架构
React v16及以后架构可分为三个部分
- Scheduler(调度器)—— 调度任务的优先级,高优先级任务优先进入Reconciler;
- Reconciler(协调器)—— VDOM的实现,负责根据状态改变计算出UI变化
- 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执行对应的操作。