一、概述
在 React 16 之前,VirtualDOM 的更新采用的是Stack架构实现的,也就是循环递归方式。不过,这种对比方式有明显的缺陷,就是一旦任务开始进行就无法中断,如果遇到应用中组件数量比较庞大,那么VirtualDOM 的层级就会比较深,带来的结果就是主线程被长期占用,进而阻塞渲染、造成卡顿现象。
为了避免出现卡顿等问题,我们必须保障在执行更新操作时计算时不能超过16ms,如果超过16ms,就需要先暂停,让给浏览器进行渲染,后续再继续执行更新计算。而Fiber架构就是为了支持“可中断渲染”而创建的。
在React中,Fiber使用了一种新的数据结构fiber tree,它可以把虚拟dom tree转换成一个链表,然后再执行遍历操作,而链表在执行遍历操作时是支持断点重启的,示意图如下。
~~java思维导图获取路径~~
二、Fiber架构
2.1 执行单元
官方介绍中,Fiber 被理解为是一种数据结构,但是我们也可以将它理解为是一个执行单元。
Fiber 可以理解为一个执行单元,每次执行完一个执行单元,React Fiber就会检查还剩多少时间,如果没有时间则将控制权让出去,然后由浏览器执行渲染操作。React Fiber 与浏览器的交互流程如下图。
可以看到,React 首先向浏览器请求调度,浏览器在执行完一帧后如果还有空闲时间,会去判断是否存在待执行任务,不存在就直接将控制权交给浏览器;如果存在就会执行对应的任务,执行完一个新的任务单元之后会继续判断是否还有时间,有时间且有待执行任务则会继续执行下一个任务,否则将控制权交给浏览器执行渲染,这个流程是循环进行的。
所以,我们可以将Fiber 理解为一个执行单元,并且这个执行单元必须是一次完成的,不能出现暂停。并且,这个小的执行单元在执行完后计算之后,可以移交控制权给浏览器去响应用户,从而提升了渲染的效率。
2.2 数据结构
在官方的文档中,Fiber 被解释为是一种数据结构,即链表结构。在链表结构中,每个 Virtual DOM 都可以表示为一个 fiber,如下图所示。
通常,一个 fiber包括了 child(第一个子节点)、sibling(兄弟节点)、return(父节点)等属性,React Fiber 机制的实现,就是依赖于上面的数据结构。
2.3 Fiber链表结构
通过介绍,我们知道Fiber使用的是链表结构,准确的说是单链表树结构,详见ReactFiber.js源码。为了放便理解 Fiber 的遍历过程,下面我们就看下Fiber链表结构。
在上面的例子中,每一个单元都包含了payload(数据)和nextUpdate(指向下一个单元的指针)两个元素,定义结构如下:
class Update { constructor(payload, nextUpdate) { this.payload = payload //payload 数据 this.nextUpdate = nextUpdate //指向下一个节点的指针 } }
接下来定义一个队列,把每个单元串联起来。为此,我们需要定义两个指针:头指针firstUpdate和尾指针lastUpdate,作用是指向第一个单元和最后一个单元,然后再加入baseState属性存储React中的state状态。
class UpdateQueue { constructor() { this.baseState = null // state this.firstUpdate = null // 第一个更新 this.lastUpdate = null // 最后一个更新 } }
接下来,再定义两个方法:用于插入节点单元的enqueueUpdate()和用于更新队列的forceUpdate()。并且,插入节点单元时需要考虑是否已经存在节点,如果不存在直接将firstUpdate、lastUpdate指向此节点即可。更新队列是遍历这个链表,根据payload中的内容去更新state的值
class UpdateQueue { //..... enqueueUpdate(u