React concurrent mode 简介

1,477 阅读3分钟

React concurrent mode 简介

Concurrent 模式是一组 React 的新功能,可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整。

目前仅在实验版本可用,React18计划加入该机制。

并发模式指非阻塞,主要包含两个场景,一个是js执行阻塞浏览器渲染,另一个是网络请求阻塞界面渲染。

对阻塞进行优化可以提升实际和感知性能。

js执行阻塞通过fiber架构及配套逻辑实现“可中断渲染”来解决。网络请求阻塞界面渲染通过suspense方案解决,其实网络请求阻塞界面渲染可以开发者自己手动实现,基本逻辑就是某个组件如果在代码或者数据还没有准备好时候,先渲染一个loading,等准备好了之后再渲染组件,suspense为这种场景提供了比较通用和使用方便的解决方案。

可中断渲染

问题

React16 以前,对virtural dom的更新和渲染是同步的。就是当一次更新或者一次加载开始以后,diff virtual dom并且渲染的过程是一口气完成的。如果组件层级比较深,相应的堆栈也会很深,长时间占用浏览器主线程,一些类似用户输入、鼠标滚动等操作得不到响应。

如果有足够的时间,浏览器是会对我们的代码进行编译优化(JIT)及进行热代码优化,一些DOM操作,内部也会对reflow进行修正。而过长时间占用主进程,会导致性能下降。

解决方案是时间分片,即利用浏览器的渲染空余时间来执行diff工作,当执行时间过长时候,停止diff工作,把执行机会让给渲染,然后等渲染间隙再继续执行diff工作,直到diff完成,再执行commit。这样不仅避免了渲染工作被js执行阻塞导致的卡顿,还让浏览器有时间对代码优化从而提升执行性能。

时间分片

如何实现时间分片呢?

基本思路是这样的

时间分片的时长

首先分片大小和渲染频率有关,肉眼能接受的流畅画面的最低帧率是60fps,即一帧16ms。因此每个时间分片不能大于16ms,如果执行时间大于16ms,就要停止,然后让浏览器先执行渲染操作,渲染空余时候再继续执行。

这就要求React拥有暂定和重启diff操作的能力。

断点重启

基于React16及之前的版本的架构很难实现断电重启功能,因为虚拟dom天然是嵌套结构,diff是递归操作。因此React团队称React16之前的调度器为栈调度器。

栈的问题,首先递归结构每次创建函数需要生成执行上下文、变量对象,性能消耗较大。另一方面,递归结构不方便进行中断重启。比如深度优先遍历的话,遍历到某个节点时候中断,再重启时候,如果没有复杂的辅助数据结构,是不知道下一个要遍历哪个节点的。

鉴于栈结构的问题,React需要新的架构来支持断点重启。

Fiber架构应运而生。

首先,需要将虚拟dom树结构改成链表结构,链表结构利于暂停和重启,比如遍历到某个节点时候需要暂停,那么只要记录当前指针,等到重启时候指向下一个就可以了。