为什么在 React 16 版本中 render 阶段放弃了使用递归?

深渊向深渊呼唤

因为主流浏览器的刷新频率为60Hz,即每(1000ms /60Hz)16.6ms浏览器刷新一次。JS可以操作DOM,GUI渲染线程与JS线程是互斥的。所以JS脚本执行和浏览器布局、绘制不能同时执行。超过16.6ms就会让用户感知到卡顿。

在16之前的版本中采用递归执行。递归耗内存,它使用 JavaScript 自身的执行栈,更新一旦开始,中途就无法中断。当VirtualDOM 树的层级很深时,virtualDOM 的比对就会长期占用 JavaScript 主线程,递归更新的时间就会超过16ms,由于 JavaScript 又是单线程的无法同时执行其他任务,所以在比对的过程中无法响应用户操作,无法即时执行元素动画,造成了页面卡顿的现象。 而React16架构可以分为三层:Scheduler,Reconciler,Renderer,与之前不同的是Reconciler和Renderer不再交替执行,而是当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟DOM打上代表增/删/更新的标记,整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer。并且采用双缓存用作统一替换,用户也不会看到更新不完全的真实dom。它放弃了 JavaScript 递归的方式进行 virtualDOM 的比对,而是采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,不会长期占用主线程,这就解决了 virtualDOM 比对造成页面卡顿的问题。

解决方案:

1. react16后使用 fiber架构可拆分,可中断任务

可重用各分阶段任务,且可以设置优先级 可以在父子组件任务间前进后退切换任务 render方法可以返回多元素(即可以返回数组) 支持异常边界处理异常

采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,不会长期占用主线程,这就解决了 virtualDOM 比对造成页面卡顿的问题。

2. 官方实现了自己的任务调度库,这个库就叫做 Scheduler。

window 对象中提供了 requestIdleCallback API ,可以实现用浏览器的空闲时间执行任务,但是它兼容性差。不过目前属于还在实验中的功能。
链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

栏目