问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

React Fiber架构的原理和工作模式

创作时间:
作者:
@小白创作中心

React Fiber架构的原理和工作模式

React在16版本中引入了Fiber架构,这是React内部工作原理的一次重大革新。Fiber架构解决了React在处理复杂组件树时的性能问题,使得React应用能够更加流畅地响应用户输入。本文将深入探讨Fiber架构的原理和工作模式,帮助读者理解React是如何实现高效渲染和更新的。

什么是Fiber?

当编写React组件并使用JSX时,React在底层会将JSX转换为元素的对象结构。这个转换过程是由Babel或类似JS编译器处理的。

JSX的本质

JSX是JavaScript的语法扩展,看起来像HTML,但实际上它是JavaScript代码。在React中,JSX会被转换为标准的JavaScript函数调用,即React.createElement()

转换机制

React.createElement是React提供的核心函数,用来创建虚拟DOM(React Element)。

  • 第一个参数:创建的HTML标签,例如h1
  • 第二个参数:属性对象props,如果没有属性则为null
  • 第三个参数:标签包含的内容,例如'Hello, world'

Babel使用插件(如@babel/plugin-transform-react-jsx)完成这个转换:

{
  "presets": ["@babel/preset-react"]
}

运行完Babel之后,输出的代码就是React.createElement形式。

Babel对JSX的转换逻辑

React.createElement的作用是构建虚拟DOM树,即一个轻量级的JavaScript对象,用来描述真实的DOM结构。React会根据这个结构最终生成并更新真实的DOM。

  • JSX是语法糖,它最终被编译为React.createElement调用。
  • React.createElement的核心作用是生成描述UI的虚拟DOM。
  • Babel等工具负责处理JSX的转换,使开发更直观高效。

虚拟DOM是React根据JSX创建的一种内部实例,用来追踪该组件的所有信息和状态。在Fiber架构中,这种内部实例被称为Fiber

Fiber是一个JavaScript对象,代表React的一个工作单元,用来追踪对应组件的所有信息和状态。

例如,Fiber对象的结构如下:

{
  type: 'h1', // 组件类型(标签类型)
  key: null, // React key
  props: {}, // 输入的props
  state: {}, // 组件的state
  child: Fiber | null, // 第一个子元素的Fiber
  sibling: Fiber | null, // 下一个兄弟元素的Fiber
  return: Fiber | null, // 父元素的Fiber
  ...其他属性
}

React的工作是沿着Fiber树进行,试图完成每个Fiber工作。如果主线程有其他工作(如响应用户输入),React可以随时终止当前工作并返回执行主线程上的任务。Fiber不仅是代表组件的一个JavaScript对象,也是React调度和更新机制的核心。

为什么需要Fiber?

在React 16之前的版本中,React使用递归的方式处理组件更新,称为堆栈调和(Stack Reconciliation)。这种方法一旦开始就无法中断,必须遍历完整个组件树。当遇到大量数据或复杂视图组件时,这可能导致主线程阻塞,React应用无法即时响应用户输入或其他高优先级任务的执行。

React 16引入的Fiber架构解决了上述问题。Fiber是React创建的一个带有链接关系的DOM树,每个Fiber代表一个工作单元。React在处理任何Fiber之前,会判断当前是否有足够时间完成该工作,并可以在必要时中断当前工作。

React 16之前版本的组件更新代码示例:

function render(element, rootParent) {
  console.log('output-> render 执行');
  // 根据元素类型创建节点
  let dom = document.createElement(element.type);
  rootParent.appendChild(dom);
  // 添加dom的属性
  Object.keys(element.props)
    .filter(prop => prop !== 'children')
    .forEach(attr => {
      dom[attr] = element.props[attr];
    });
  // 将子元素进行渲染
  if (Array.isArray(element.props.children)) {
    element.props.children.forEach(child => {
      render(child, dom);
    });
  } else {
    dom.innerHTML = element.props.children;
  }
}

Fiber的结构

function FiberNode(
  this: $FlowFixMe,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // 基本属性
  this.tag = tag; // 描述此Fiber的启动模式的值(LegacyRoot=0;ConcurrentRoot=1)
  this.key = key; // React key
  this.elementType = null; // 描述React元素的类型,例如 JSX<App/> 则elementType是App
  this.type = null; // 组件类型
  this.stateNode = null; // 对于类组件,这是类组件的实例;对于DOM元素,是对应的DOM节点

  // Fiber链接
  this.child = null; // 指向第一个子Fiber
  this.sibling = null; // 指向其兄弟Fiber
  this.return = null; // 指向父Fiber
  this.index = 0; // 子Fiber中的索引位置
  this.ref = null; // 如果组件上有ref属性则该属性指向它
  this.refCleanup = null; // 如果组件上的ref属性在更新中被删除或更改,此字段会用于追踪需要清理的旧ref

  // Props & State
  this.pendingProps = pendingProps; // 正在等待处理的新的props
  this.memoizedProps = null; // 上一次渲染时的props
  this.updateQueue = null; // 一个队列,包含了该Fiber上的状态更新和副作用
  this.memoizedState = null; // 上一次渲染时的state
  this.dependencies = null; // 该Fiber订阅的上下文或其他资源的描述
  // 工作模式
  this.mode = mode; // 描述Fiber工作模式的标志(eg: Concurrent模式、Blocking模式等

  // Effects
  this.flags = NoFlags; // 该Fiber发生的副作用标志
  this.subtreeFlags = NoFlags; // 描述该Fiber子树中发生的副作用的标志
  this.deletions = null; // 在commit阶段要删除的子Fiber数组

  this.lanes = NoLanes; // 与React的并发模式有关的调度概念。
  this.childLanes = NoLanes; // 与React的并发模式有关的调度概念。
  this.alternate = null; // Current Tree和Work-in-progress (WIP) Tree的互相指向对方tree里的对应单元
}

Fiber是一个更强大的虚拟DOM。

Fiber的工作原理

Fiber工作原理最核心的特性是可以中断和恢复,这增强了React的并发性和响应性。实现可中断和恢复的原因是Fiber提供的信息让React可以追踪工作进度、管理调度和同步更新到DOM。

Fiber工作原理的关键点:

  • 工作单元:每个Fiber节点代表一个工作单元,所有Fiber节点共同组成一个Fiber链表树(具有链接属性和树的结构),让React可以细粒度控制节点的行为。
  • 链接属性childsiblingreturn字段构成Fiber之间的链接关系,使得React能够遍历组件树并知道从哪里开始、继续或停止工作。
  • 双重缓冲技术:React在更新时,会根据现有的Fiber树(Current Tree)创建一个新的临时树(Work-in-progress (WIP) Tree)。WIP Tree包含了当前更新受影响的最高节点直至其所有子孙节点。Current Tree是当前显示在页面上的视图,WIP Tree则是在后台进行更新。WIP Tree更新完成后会复制其他节点,并最终替换掉Current Tree,成为新的Current Tree。因为React在更新时总是维护了两个Fiber树,所以可以随时进行比较、中断或恢复等操作,而且这种机制让React能够同时具备优秀的渲染性能和UI的稳定性。
  • State和PropsmemoizedPropspendingPropsmemoizedState字段让React知道组件的上一个状态和即将应用的状态。通过比较这些值,React可以决定组件是否需要更新,从而避免不必要的渲染,提高性能。
  • 副作用的追踪flagssubtreeFlags字段标识Fiber及其子树中需要执行的副作用,例如DOM更新、生命周期方法调用等。React会积累这些副作用,然后在Commit阶段一次性执行,从而提高效率。

Fiber的工作流程

Fiber的工作流程分为两个阶段:调和(Reconciliation)提交(Commit)

第一阶段:调和(Reconciliation)

目标:确定哪些部分的UI需要更新。

原理:这是React构建工作进度树的阶段,会比较新的props和旧的Fiber树来确定哪些部分需要更新。

调和细分为两个小阶段:

  1. 创建与标记更新节点beginWork
  • 判断Fiber节点是否要更新
  • 判断Fiber子节点是更新还是复用
  1. 收集副作用列表completeUnitOfWorkcompleteWork

第二阶段:提交(Commit)

目标:更新DOM并执行任何副作用。

原理:遍历在Reconciliation阶段创建的副作用列表进行更新。

在源码中,commitRootcommitRootImpl是提交阶段的入口方法。提交阶段也有三个核心小阶段:

  1. 遍历副作用列表BeforeMutation
  2. 正式提交CommitMutation
  3. 处理layout effectscommitLayout

从源码中可以看出,一旦进入提交阶段后,React是无法中断的。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号