栈协调 与 Fiber协调 有啥区别?

Virtual DOM 是一种编程概念。在这个概念里, UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。这一过程叫做协调。 —— React 官方

换句话说:协调指的是虚拟DOM映射到真实DOM的过程,是“找一致”的过程。协调器所做的工作也是一系列的:包括组件的挂载、卸载和更新等过程。而目前大众认知里,讨论协调时默认就是讨论Diff,主要是协调器更新过程涉及到Diff的调用,同时也是协调过程中最具代表性的一环。

Diff的过程是“找不同”,并以其实现形式的不同,协调过程被划分为以React15为代表的“栈协调”和以React16为代表的“Fiber协调”

一、栈协调

由于目前最前沿的算法中找出两棵树结构之间的不同其开销非常大,复杂程度达到 O(n 3 ),其中 n 是树中元素的数量,这不能满足React团队在用户体验中最为要紧的一个追求:快速响应。

因此,React团队 基于 两个不同类型的元素会产生出不同的树开发者可以通过 key prop 来暗示哪些子元素在不同的渲染下能保持稳定 的假设,结合DOM节点之间的跨层级操作并不多,同层级操作是主流 的规律,提出了一套 O(n) 的启发式算法:

  • Diff性能突破的关键点在于“分层对比”
  • 类型一致的节点才有继续Diff的必要性
  • key属性的设置,可以帮助我们尽可能复用同一层级内的节点

1、分层对比:改变时间复杂度的决定性思路

React 的 Diff 过程直接放弃跨层级的节点比对,只针对相同层级的节点比对。但整个算法仍然是以递归的形式运转,只是分层递归

2486b134438e67ffundefined

对于跨层级节点操作,React并不能判断出“移动”这个行为,它只能机械的认为,移出的那一层组件不要了,对应的子树需要销毁;而移入的那一层组件则为新增。即React对于跨层级节点操作是:销毁+新增,这代价是昂贵的,因此React官方建议尽量保持DOM结构的稳定性,建议开发者不要进行跨层级节点操作。

91802856ca488461undefined

2、类型一致:减少递归“一刀切”策略

若参与Diff的两个组件类型不同,那么直接放弃比较,原地替换掉旧的节点。减少Diff过程中冗余的递归操作

20fb0442bdae3718undefined

3、key属性:重用节点的关键

key作为一个节点的唯一标识,试图解决同一层级下节点的复用问题,在使用时应确认其唯一性和稳定性。

50c3cef7eff8e818undefined

上图若没有key属性,其Diff过程如下:

  • 对比第一层的节点:两棵树的节点类型一致(都是A),进入第二层比对
  • 第一个节点位置都是B,进入第二个节点比对
  • 第二个节点位置C,比对C和D,其类型不一致,直接删掉C重建D
  • 第三第四个节点位置在树1中是空的,分别新增E节点和C节点

而当有了key属性,React会通过识别ID,意识到C并没有发生改变,只是被调整了顺序而已,接着React将C节点移动到新位置,并将D、E节点插入到树中

因此,当我们在基于数组动态生成节点时,最好给每个节点添加key属性,若你忘了,React会抛出警告:“请给列表元素补齐key属性”。

二、无解的问题

栈协调机制下的Diff算法,其实是树的深度优先遍历过程,也就是一个同步的递归过程,意味着一旦更新开始,根本停不下来。当处理复杂度高、体量大的虚拟DOM树时,栈协调需要的调和时间会很长,意味着JavaScript 对主线程将长时间占用,进而导致渲染卡顿、无响应等问题

三、Fiber协调

按照React官方的说法,是实现增量渲染,换句话说就是把一个渲染任务分解为多个渲染任务,而后将其分散到多个帧里,是一种手段。

实现增量渲染的目的,是为了实现任务的可中断、可恢复,并给不同的任务赋予不同的优先级,最终达到快速响应的体验

1、Fiber协调核心:“可中断”、“可恢复” 和 “优先级”

透过React的渲染和更新阶段的依赖,来啾啾Fiber协调的核心

相对于栈协调,Fiber协调多出了调度器Scheduler,其更新的处理工作变成了

  • 每个更新任务都会赋予一个优先级
  • 当更新任务抵达调度器时,高优先级的更新任务(A)会更快地被调度进入 Reconciler —— 优先级
  • 此时有新的更新任务(B),调度器会检查它优先级,若高于当前任务(A),处于当前Reconciler层的A任务会被中断,调度器将B任务推入Reconciler层 —— 可中断
  • 当B任务完成渲染后,新一轮调度开始,之前被中断的A任务将会被重新推入Reconciler层,继续它的渲染 —— 可恢复

2、React 架构分层与生命周期

首先我们回顾一下生命周期里的 render 和 commit 阶段

  • render阶段:React主要是在内存中做计算,明确DOM树的更新点
  • commit阶段:负责把 render 阶段生成的更新真正地执行掉

可以看出,新老协调对生命周期的影响主要是 render 这个阶段,Fiber协调 通过增加 Scheduler 层 和 改写 Reconciler层 实现将一个庞大的更新任务分解为一个个的工作单元,这些工作单元有着不同的优先级,React 可以根据优先级的高低去实现工作单元的打断和恢复