React18:concurrent的粗略探索

1,353 阅读5分钟

concurrent作为react团队提了几年的ui模式,这么久了一直没有在实际工程中落地。虽然我们现在使用的react的reconcile核心已经为concurrent模式从stack重构成了fiber,但现在的react更新仍然是同步模式而不是concurrent模式。react 17版本没有breaking change和新增的api,主要就是为了concurrent模式过渡,或许在react 18中有希望正式发布,所以简单的做一个concurrent模式的前瞻。

加入我们,一起搞事

什么是concurrent

concurrent主要指的是“可中断渲染”,并不是真的并发,因为js本身是单线程的。渲染可中断,就可以保持应用快速响应,避免阻塞,可以定义渲染的优先级,让要更优先进行的渲染工作先完成。在没有concurrent模式之前,任何react的组件的更新都是自顶向下递归完成,中间不会被打断,在有了concurrent模式之后,我们就可以避免这种情况,让一些更高优先级的打断渲染的过程,比如动画,用户输入等等。react concurrent模式刚提出的时候网上有 demo 比对过两种模式的差异,在这个demo里面可以看到concurrent模式下页面的动画相对流畅很多,不过是4年前的demo了,接下来会使用最新的react的api重构这个demo,体验应用concurrent模式带来的变化。

Sync更新模式造成卡顿

我们渲染了一个很多节点的谢尔宾斯基三角形,通过 raf 用js写了一个伸缩的动画,并设置一个定时器,每一秒钟去更新所有节点的文本,代码如下 在实际的渲染中发现动画比较卡顿,因为每次更新所有节点的文本花费了大量的时间,打开performance面板看一下具体的js执行。 可以看到,每次更新都是js执行超过1s的long task,在这期间raf根本没机会执行,动画自然也会卡顿。

使用concurrent模式

如果使用了concurrent模式会怎么样呢,启用concurrent模式很简单,代码如下 使用了concurrent模式之后,页面的卡顿并没有什么变化,打开performance面板看看js的执行 可以看到js的执行已经变成了一块一块的执行阶段,不再是超过1s不会中断的情况,我们已经拥有了可中断的更新。但由于我们并没有设置更新的优先级,在更新所有节点的过程中,不会有更高优先级的更新去抢占渲染,所以每个时间片更新完之后在下个任务队列就会立即继续更新流程,页面还是会卡顿。那么接下来如果我们把raf的js动画设置更高的优先级,让动画的更新优先于节点文本的更新,是不是页面就会看起来流畅了呢。

设置更新的优先级

那么该如何设置优先级呢,在react concurrent模式有 useTranstition 这个hook可以设置更新的过期时间,可以延后组件的更新到过期时间之后。那么我们对于更新所有节点文本这个更新流程设置一下延迟的时间,代码如下。 对于文本的更新,我们设置了300毫秒的过期时间,可以看到动画明星流畅了很多,再打开performance面板看看 可以看到很多紫色的表示计算样式的执行,说明我们设置的每一帧的raf动画样式都得到了及时的计算,动画也因此流畅了很多,如果细心的观察,现在跟之前提到的4年前的demo比起来还是会有一下明显的卡顿,4年前的demo全程都是很流畅的,这是为什么呢。因为现在react的reconcile阶段中,update是可以中断的,但commit阶段是不可以中断的,为了保证ui更新的一致性,commit是同步阶段,所以每次同步commit更新所有节点的文本也是比较耗时的,会造成卡顿 可以看到在众多的时间片中,commit阶段是没有中断的。

总结与展望

concurrent模式确实给现在的react更新带来了更多的可能性,但concurrent并不是银弹,在本文的例子中,组件其实是用memo包裹过的,如果不用memo包裹,那么每次更新动画顺带着还会更新所有文本,虽然是一样的文本,但还是会花费比较多的时间,动画还是会卡顿。而且useTransition还有个返回值isPending表示是否正在延迟更新,所以startTransition后的其实是进行了两次更新,一次是立即更新当前组件更新isPending状态,第二次就是延迟的更新,组件的渲染次数变多了。因此memo和shouldComponentUpdate这类api的使用会更加重要。但其实本人在日常开发中很少用到这类阻止重渲染的api,因为需要比较重的心智负担,必须要保证immutable数据流,但由于js原生就不支持,手写保证immutable数据流只要有一个地方是mutable的组件的子树全部前功尽弃。因此最好使用 immer 这类库保证immutable,但又会带来一定的学习成本。 总之,concurrent模式有好处,学习和使用成本也不一定低,那该怎么办呢? 用这图尤大不会打人吧 用这图尤大不会打人吧