React15和React16的架构比较(二)

1,337 阅读3分钟

上一篇文章聊到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对象如何实现渲染进程可分段完成呢?

  1. 首先,DOM组件实例均对应一个fiber实例,fiber实例负责管理组件实例的更新,渲染任务及与其他fiber实例的联系。

  2. 其次,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架构中整个更新流程为:

image.png

其中红框中的步骤随时可能由于以下原因被中断:

  • 有其他更高优任务需要先更新
  • 当前帧没有剩余时间

由于红框中的工作都在内存中进行,不会更新页面上的DOM,所以即使反复中断,用户也感知不到。