我们知道浏览器的GUI渲染线程JavaScript引擎线程互斥的当其中一个线程执行时,另一个线程只能挂起等待。

在JavaScript中,如果某段代码长时间占用主线程,也就是称为”阻塞”,那么渲染层面的更新会被延迟,导致页面出现卡顿的感觉。这是因为JavaScript是单线程执行的,也就是说它一次只能执行一个任务,如果有某个任务执行时间过长,其他任务就必须等待。

在旧版的React(例如React 15)中,采用了Stack Reconciler的方式来渲染组件。这种方式下,React会一口气完成整个组件的渲染过程,中途无法中断。如果组件较为复杂,计算和渲染过程可能会耗费较长时间,导致主线程长时间被占用。在这段时间里,用户的交互和动画等任务无法得到及时处理,从而使页面的响应度变差,用户可能感受到页面出现卡顿的现象。

什么是Fiber架构

为了解决这个问题,React引入了Fiber架构。Fiber架构使得React能够将渲染过程拆分成多个小任务,每个小任务只执行一小部分工作,然后将控制权交回给主线程,这样就允许了主线程在执行每个小任务之间处理其他任务,比如响应用户交互。通过这种方式,React能够更加灵活地处理任务优先级,提高页面的响应性,减少卡顿的情况。所以,React Fiber的引入显著改善了React应用的性能和用户体验。

React Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现。从Facebook在 React Conf 2017 会议上确认,React Fiber 在React 16 版本发布。

react中,主要做了以下的操作:

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(链表),对应两个队列,这都是为找到被中断的任务,重新执行

在 React Fiber 架构中,DOM diff 的过程被拆分成链表形式的数据结构,这个链表被称为 Fiber 链表。每个 DOM 节点对应两个 Fiber 节点,一个称为当前 Fiber 节点(current fiber),另一个称为工作中的 Fiber 节点(work-in-progress fiber)。这两个 Fiber 节点一起形成了一对,代表了同一个 DOM 节点在不同时间点的状态。

为了在渲染过程中找到被中断的任务并重新执行,React 使用了两个队列:提交队列(commit queue)和工作队列(work queue)。

  1. 提交队列(commit queue): 提交队列用于存储已经完成工作的 Fiber 节点,即渲染过程中已经计算完成的节点。在渲染过程的最后,React 会将提交队列中的 Fiber 节点一次性地更新到真实的 DOM 中,从而保证一次完整的 DOM 更新。

  2. 工作队列(work queue): 工作队列用于存储尚未完成工作的 Fiber 节点,即渲染过程中还需要继续计算的节点。当 React 开始渲染过程时,它会从工作队列中取出一个 Fiber 节点并开始计算该节点对应的 DOM 更新。然后,如果有更高优先级的任务进来,React 可以中断当前任务,并将未完成的工作保存在工作队列中,以便稍后重新开始。

这种链表和队列的设计使得 React Fiber 能够更灵活地处理任务优先级,提供了更好的交互性和用户体验。当有更紧急的任务需要处理时,React 可以中断当前任务,将中间状态存储在工作队列中,然后立即处理更重要的任务,从而减少用户感知的卡顿和延迟。当更高优先级的任务处理完毕后,React 可以再次回到工作队列,继续完成之前中断的任务,最终保证页面的正确更新。这样的机制使得 React 能够更加高效地执行 DOM 更新,提供更流畅的用户体验。

从架构角度来看,Fiber是对React核心算法(即调和过程)的重写。

从编码角度来看,FiberReact内部所定义的一种数据结构,它是Fiber树结构的节点单位,也就是React 16新架构下的虚拟DOM。

一个fiber就是一个JavaScript对象,包含了元素的信息、该元素的更新操作队列、类型等等。

Fiber架构的实现

在 React 中,Fiber 把渲染更新过程拆分成多个子任务,并通过协作式调度的方式来执行这些任务。每个任务都对应一个 React element,并由对应的 Fiber 节点来表示。

具体实现的方式是通过 requestIdleCallback() 方法来调度任务。requestIdleCallback() 方法会在浏览器空闲时段调用注册的回调函数,这样可以在主事件循环上执行后台和低优先级的工作,而不会影响关键的事件,如动画和输入响应。

React 利用 requestIdleCallback() 方法将任务切割成多个小步骤,分批完成。每次执行一个小步骤后,React 会检查是否还有剩余时间。如果有剩余时间,它会继续执行下一个任务。如果没有剩余时间,React 会主动放弃控制权,将时间控制权交还给主线程,以便浏览器可以继续进行页面的渲染或响应其他高优先级的任务。

当浏览器空闲时,React 再次获得控制权,它会恢复之前被中断的任务,复用之前的中间状态,然后继续执行剩余的工作。这样的任务中断和恢复机制,使得 React 能够更加灵活地处理任务优先级,提高页面的响应性,减少用户感知的卡顿和延迟。

总结起来,React Fiber 将渲染更新过程分解成多个子任务,并借助 requestIdleCallback() 实现协作式调度,使得任务可以中断与恢复,复用中间状态,并根据任务的优先级合理地安排执行顺序,从而提高整体渲染性能和用户体验。

每个 Fiber 节点有个对应的React element,多个Fiber节点根据如下三个属性构建一颗树:

// 指向父级Fiber节点this.return = null// 指向子Fiber节点this.child = null// 指向右边第一个兄弟Fiber节点this.sibling = null

通过这些属性可以辅助React寻找到下一个执行目标,从而完成下一次更新。