上一篇文章聊到React15架构存在不能支撑异步更新的情况以至于需要重构。那么下面来学习一下React16是如何支持异步更新的。
React16架构
React16架构可以分为三层:
- Scheduler(调度器)—— 负责调度任务的优先级,高优任务优先进入Reconciler
- Reconciler(协调器)—— 负责找出需要更新的组件
- Renderer(渲染器)—— 负责将需要更新的组件渲染到页面上
可以看到,相较于React15,React16中新增了
Scheduler
调度器,让我们来了解下它。
Scheduler(调度器)
浏览器线程执行任务时会以帧(一帧16.6ms)的形式划分,在两个执行帧之间,主线程通常会有一小段空闲时间,称为空闲期。
目前主流浏览器提供了 requestIdleCallback
API,支持在空闲期内调用空闲期回调,执行一些任务。与之相对的,高优先级任务由requestAnimationFrame API执行,如动画等。利用任务优先级高低,分别调度执行函数,这样就可以解决React15的交互卡顿问题。
React 16 基于这个原理实现了功能更完备的requestIdleCallback polyfill
—— Scheduler。
Reconciler(协调器)
“fiber” reconciler 是一个新尝试,致力于解决 stack reconciler 中固有的问题,同时解决一些历史遗留问题。Fiber 从 React 16 开始变成了默认的 reconciler。
主要工作:
- 能够把可中断的任务切片处理。
- 能够调整优先级,重置并复用任务。
- 能够在父元素与子元素之间交错处理,以支持 React 中的布局。
- 能够在 render() 中返回多个元素。
- 更好地支持错误边界。
在v16中 Fiber Reconciler 通过中引入新的数据结构——Fiber对象,配合Scheduler解决了Stack Reconciler的缺陷。即渲染进程可分段完成,而不必一次性完成,期间优先执行其他高优先级任务。 暂停处理如下:
/** @noinline */
function workLoopConcurrent() {
// 执行工作直到调度器要求我们暂停
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}
因此更新工作从递归变成了可中断的循环过程,每次循环都会调用shouldYield
判断当前是否有剩余时间。
那么,Fiber对象如何实现渲染进程可分段完成呢?
-
首先,DOM组件实例均对应一个fiber实例,fiber实例负责管理组件实例的更新,渲染任务及与其他fiber实例的联系。
-
其次,fiber的工作是在内存中进行的,不会对页面造成影响,用户感知不到,处理后的结果交由 Render处理。
在对数据的处理过程中,当任务交给Reconciler后,Reconciler会为更新的虚拟DOM打上代表增/删/更新的标记,类似这样:
export const Placement = /* */ 0b0000000000010;
export const Update = /* */ 0b0000000000100;
export const PlacementAndUpdate = /* */ 0b0000000000110;
export const Deletion = /* */ 0b0000000001000;
...
Renderer(渲染)
Renderer与v15的类似,不同的是,其根据Reconciler为虚拟DOM打的标记,同步执行对应的DOM操作。
对于我们在上一篇文章中使用过的例子,在React16架构中整个更新流程为:
其中红框中的步骤随时可能由于以下原因被中断:
- 有其他更高优任务需要先更新
- 当前帧没有剩余时间
由于红框中的工作都在内存中进行,不会更新页面上的DOM,所以即使反复中断,用户也感知不到。