React Fiber

2023/03/23

1. 为什么出现Fiber?

JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待。如果 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿
而这也正是 React 15 的 Stack Reconciler所面临的问题,当 React在渲染组件时,从开始到渲染完成整个过程是一气呵成的,无法中断
如果组件较大,那么js线程会一直执行,然后等到整棵VDOM树计算完成后,才会交给渲染的线程
这就会导致一些用户交互、动画等任务无法立即得到处理,导致卡顿的情况

2. Fiber特性

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

  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
  • 支持增量渲染,fiber将react中的渲染任务拆分到每一帧。(不是一口气全部渲染完,走走停停,有时间就继续渲染,没时间就先暂停)
  • 支持暂停,终止以及恢复之前的渲染任务。(没渲染时间了就将控制权让回浏览器)
  • 通过fiber赋予了不同任务的优先级。(让优先级高的运行,比如事件交互响应,页面渲染等,像网络请求之类的往后排)
  • 支持并发处理(结合第3点理解,面对可变的一堆任务,react始终处理最高优先级,灵活调整处理顺序,保证重要的任务都会在允许的最快时间内响应,而不是死脑筋按顺序来)

3. Fiber原理

  1. Fiber把渲染更新过程拆分成多个子任务,每次只做一小部分,做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行

  2. 即可以中断与恢复,恢复后也可以复用之前的中间状态,并给不同的任务赋予不同的优先级,其中每个任务更新单元为 React Element 对应的 Fiber节点 实现的上述方式的是requestIdleCallback方法

  3. window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应

  4. 首先 React 中任务切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间再进行页面的渲染。等浏览器忙完之后有剩余时间,再继续之前 React 未完成的任务,是一种合作式调度。

  5. 该实现过程是基于 Fiber节点实现,作为静态的数据结构来说,每个 Fiber 节点对应一个 React element,保存了该组件的类型(函数组件/类组件/原生组件等等)、对应的 DOM 节点等信息。

  6. 作为动态的工作单元来说,每个 Fiber 节点保存了本次更新中该组件改变的状态、要执行的工作。

    • 这样设计的好处就是在数据层已经在不同节点的关系给描述了出来,即便某一次任务被终止,当下次恢复任务时,这种结构也利于react恢复任务现场,知道自己接下来应该处理哪些节点。
    • 通过fiber的协调阶段,我们了解了diff的对比过程,如果将fiber的结构理解成一棵树,那么这个过程本质上还是深度遍历,其顺序为父—父的第一个孩子—孩子的每一个兄弟。
    • 通过源码,我们了解到react的diff是同层比较,最先比较key,如果key不相同,那么不用比较剩余节点直接删除,这也强调了key的重要性,其次会比较元素的type以及props。而且这个比较过程其实是拿旧的fiber与新的虚拟dom在比,而不是fiber与fiber或者虚拟dom与虚拟dom比较,其实也不难理解,如果key与type都相同,那说明这个fiber只用做简单的替换,而不是完整重新创建,站在性能角度这确实更有优势。

4. Fiber执行过程

fiber在渲染中每次都会经历协调Reconciliation与提交Commit两个阶段。

协调阶段:这个阶段做的事情很多,比如fiber的创建diff对比等等都在这个阶段。在对比完成之后即等待下次提交,需要注意的是这个阶段可以被暂停。

提交阶段:将协调阶段计算出来的变更一次性提交,此阶段同步进行且不可中断(优先保证渲染)。

fiber调度