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

React16 => React引入Fiber架构,解决过去更新机制的问题 =》 在长时间的更新过程中,主线程会被阻塞,导致应用无法即时响应用户输入。
核心内容: 【Fiber是什么】【Fiber的底层原理】

Fiber是什么?

当编写React组件并使用JSX时,React在底层会将JSX转换为元素的对象结构

转换的过程是一个工具链完成,由Babel或类似JS编译器处理JSX的编译工作

  • JSX的本质
    JSX是JS的语法扩展,和HTML有点像,但实际是JS代码,在React中,JSX会被转换为标准的JS函数调用,即React.createElement()

  • 转换机制

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

  • 第一个参数:创建的HTML标签,eg: h1

  • 第二个参数:属性对象 props, 上图无属性即null

  • 第三个参数:标签包含的内容,eg: 'Hello, world'

Babel使用插件 eg: @babel/plugin-transform-react-jsx完成转换

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

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

Babel对JSX的转换逻辑

React.createElement作用:【构建虚拟DOM树】

React使用React.createElement来构建虚拟DOM树,即一个轻量级的JS对象,用来描述真实的DOM结构

虚拟 DOM 的结构,React 会根据这个结构最终生成并更新真实的 DOM。

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

虚拟DOM是React根据JSX创建一种内部实例,用来追踪该组件的所有信息和状态。

早期称之为实例或虚拟DOM对象,在Fiber架构中,称之为【Fiber】

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

Eg: 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工作,eg: 比较新旧props决定是否更新组件等
如果主线程有工作(eg: 响应用户输入)则React可随时终止当前工作并返回执行主线程上的任务。

Fiber不仅是代表组件的一个JS对象也是React调度和更新机制的核心

为啥要Fiber???

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

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

React16之前版本采用的循环递归更新组件
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元素的类型,eg: 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可细粒度控制节点的行为

  • 链接属性:child、sibling和return字段构成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和Props: memoizedProps、pendingProps 和 memoizedState 字段让React知道组件的上一个状态和即将应用的状态。通过比较这些值,React可以决定组件是否需要更新,从而避免不必要的渲染,提高性能。

  • 副作用的追踪:flags 和 subtreeFlags 字段标识Fiber及其子树中需要执行的副作用,例如DOM更新、生命周期方法调用等。React会积累这些副作用,然后在Commit阶段一次性执行,从而提高效率。

Fiber的工作流程 分为两个阶段: 【Reconciliation-调和】【Commit-提交】

第一 调和

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

  1. 创建与标记更新节点:beginWork
  • 判断Fiber节点是否要更新:
  • 判断Fiber子节点是更新还是复用:
  1. 收集副作用列表:completeUnitOfWork和completeWork
第二 提交

目标: 更新DOM并执行任何副作用。
原理: 遍历在Reconciliation阶段创建的副作用列表进行更新。
码里 commitRoot 和 commitRootImpl 是提交阶段的入口方法,在两个方法中,可以看出来提交阶段也有三个核心小阶段

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

从源码里我们可以看到,一旦进入提交阶段后,React是无法中断的。
https://weijunext.com/article/dive-into-react-fiber#第二阶段:Commit(提交)

posted @ 2024-11-23 06:51  Felix_Openmind  阅读(12)  评论(0编辑  收藏  举报