React源码解析(二)—— Fiber

561 阅读5分钟

一、概述

一、Fiber架构

Fiber架构主要是React用于提升框架性能和调度能力而引入的机制

二、为什么需要Fiber

在使用Fiber架构之前, React采取 堆栈调和(Stack Reconciliation) 的方式去处理组件树的更新,主要通过递归的方式去处理组件树,这种方法在处理大规模和高交互性应用时会存在问题:

  1. 没有异步渲染能力:渲染是同步进行的,一旦开始,就会一直执行到结束,无法中断,这可能导致长时间占用主线程,影响用户体验。

  2. 无法处理任务优先级:所有更新任务都是同等对待,没有优先级之分,这可能导致一些紧急的更新被低优先级的任务阻塞。

  3. 错误处理能力弱:一旦出现错误,可能导致整个组件树的渲染失败。

  4. 更新的可控性弱:更新是不可中断的,一旦开始,必须完成整个组件树的更新。

  5. 性能问题:使用递归的方式遍历组件树,这在大型应用中可能导致性能问题。

从下面的图可以直观的感受到新旧架构的区别:使用了Fiber架构的情况下明显渲染更为顺畅

v2-dfc766284d949d648915a2bb4ade541c_b.gif

v2-3f8e52689eb9197d6ff546a2f224ae94_b.gif

二、Fiber的结构

在React的Fiber架构中,Fiber节点是构成Fiber树的基本单元,代表了React组件的虚拟DOM。每个Fiber节点对应于屏幕上的某个组件或DOM元素,并且包含了渲染该组件或元素所需的所有信息。每个Fiber是独立的工作单元,这使得渲染过程变得更容易调度。

Fiber的数据结构

源码中Fiber的结构如下:

function FiberNode(
  this: $FlowFixMe,
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;
  this.key = key;
  // react元素的属性
  this.elementType = null;
  this.type = null;
  // 指向组件实例的引用,对于类组件来说,是一个指向实例的对象。
  this.stateNode = null;

// Fiber节点的父子关系和兄弟关系链表指针。
  this.return = null; //指向父节点
  this.child = null; // 子节点
  this.sibling = null; // 兄弟节点
  this.index = 0; // 在父节点中的索引

  this.ref = null; // 组件的ref属性,用于获取DOM元素或组件实例的引用。
  this.refCleanup = null;  // 用于清理ref相关的操作。

  // 组件的props和state的当前和上一次渲染的版本。
  this.pendingProps = pendingProps; // 等待处理的props
  this.memoizedProps = null; //上一次渲染时的props
  this.updateQueue = null; // 存储更新的队列
  this.memoizedState = null; //上一次渲染时的state
  this.dependencies = null; //Fiber依赖的上下文

  this.mode = mode; //工作模式

  // Effects
  // 用于标记Fiber节点的副作用标志
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  // 用于存储需要删除的子节点
  this.deletions = null;

  // 用于控制Fiber节点的渲染优先级。
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  // 指向Fiber节点的另一个版本,用于双缓存机制。
  this.alternate = null;

  // 处理性能分析和调试。
  if (enableProfilerTimer) {
     // ...省略
  }


  // 开发模式下调试
  if (__DEV__) {
   // ...省略
  }
}

Fiber树

依赖于Fiber, React会将组件JSX描述为如下基于链表的树结构

image.png

React遍历Fiber树的时候以深度优先(DFS) 的方式进行,遍历的顺序是Fiber节点的子节点、Fiber节点本身、Fiber节点的兄弟节点。在这个过程中Fiber树中每一个节点都可以看作是一个独立的渲染任务,提高了处理页面渲染过程中的精度和可调度性。

而当有节点需要更新时:

除了用于显示当前UI的树current外,Fiber还会创建一个workInProgress树,变动的更新会在其上进行处理,处理完毕后用于下一次渲染,并替代之前的current树变为新的current树。

image.png

为什么使用链表的结构代替堆栈能够解决问题

在页面绘制过程中,浏览器是一帧一帧去处理的,当FPS低于60即每一帧的绘制时长超过16ms的情况下,操作者会感觉到卡顿。

image.png

在使用之前的堆栈调和(Stack Reconciliation) 的方式进行渲染时由于调用的是一个,一旦执行开始除非全部运行完毕否则无法暂停,当组件或者元素的数量较多的情况下,会使得JS执行阶段花费过多的时间,导致下一帧不能及时绘制导致产生卡顿感

而在Fiber架构中,通过使用指针指向当前处理的Fiber节点的方式,可以灵活的处理渲染过程,即使操作被暂停,也能够根据已经处理完成的元素快速找到下一个需要处理的元素。同时由于其灵活性,我们可以对其进行规划,采取一定的调度方法,将任务分解为时间片,并且定义每一个任务单元的优先级等,这样确保更新任务只能在一定时间内占用浏览器主线程,不会影响当前帧中后续的操作,获得更顺畅的用户体验。

总结

Fiber架构是React引入的一种性能优化机制,它通过链表结构和双缓存机制,提供了异步渲染、任务优先级处理、更好的错误处理和更新可控性,从而解决了传统堆栈调和方法在大规模和高交互性应用中的性能问题。这使得React应用的渲染更加高效和流畅。