【react】浅谈react fiber架构

浅谈react fiber架构

相关文章

浅谈react fiber架构


一、fiber是什么

React Fiber 是 React 16 引入的一种新的协调引擎。它是 React 内部的一种架构重构,旨在提高 React 应用的性能和用户体验。Fiber 的目标是实现一种增量式的、可中断的渲染机制,以支持更好的并发控制、优先级调度、更精细的控制渲染流程等功能。

具体来说,React Fiber 的主要特点包括:

增量式渲染:

Fiber 架构使得 React 可以将渲染工作拆分成小块,以便在多个帧中分布工作,从而避免阻塞主线程。这种增量式的渲染方式使得 React 应用在进行大量计算或复杂布局时,仍然可以保持响应性。

可中断的渲染:

Fiber 允许 React 在渲染过程中中断任务,并在稍后恢复,这使得 React 可以更好地响应用户交互和系统事件,从而提供更流畅的用户体验。

优先级调度:

Fiber 引入了任务调度器,可以根据任务的优先级和截止时间安排任务的执行顺序。这使得 React 可以优先处理用户关键操作,从而提高应用的交互响应性。

更精细的控制渲染流程:

Fiber 架构允许 React 以更细粒度的方式控制渲染流程,包括组件的生命周期、副作用处理、错误边界等方面。这使得 React 可以更好地管理组件的状态和生命周期,提高了应用的稳定性和可靠性。

总的来说,React Fiber 架构是 React 内部的一种重大改进,旨在提高 React 应用的性能、可伸缩性和用户体验。通过引入增量式渲染、可中断的渲染、优先级调度等功能,Fiber 架构使得 React 更适合于处理复杂的交互和动态变化的界面。

二、为什么引入fiber

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

详细参考:react 15-16架构变化

三、fiber工作原理

Fiber节点

在 React Fiber 架构中,一个组件通常对应一个 Fiber 节点。这个 Fiber 节点包含了该组件的相关信息,比如组件类型、props、state 等,以及与该组件相关的渲染状态、更新队列等信息。这个 Fiber 节点会被用于管理该组件的生命周期、更新和渲染。

然而,在某些情况下,一个组件可能会对应多个 Fiber 节点。这种情况通常出现在 React 的调和过程中,特别是在对应变化的情况下。例如,当组件拥有多个子组件,或者组件在更新过程中发生了拆分、合并等操作时,就可能会创建多个 Fiber 节点来代表这些不同的组件状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
function FiberNode(
tag: WorkTag, // 标记此 Fiber 节点的类型,比如 FunctionComponent、HostComponent 等
pendingProps: mixed, // 待处理的 props,即组件在调和过程中的 props
key: null | string, // 组件的 key
mode: TypeOfMode, // 当前 Fiber 节点的渲染模式,比如 ConcurrentMode、StrictMode 等
) {

// 标记此 Fiber 节点的类型
this.tag = tag;

// 组件的 key
this.key = key;

// 未使用
this.elementType = null;

// 组件类型或 DOM 元素类型
this.type = null;

// 表示此 Fiber 节点对应的组件实例或 DOM 节点
this.stateNode = null;

// 用于连接其他 Fiber 节点形成 Fiber 树的关系
this.return = null; // 指向父级 Fiber 节点
this.child = null; // 指向第一个子级 Fiber 节点
this.sibling = null; // 指向下一个兄弟 Fiber 节点
this.index = 0; // 在父级的子级列表中的索引位置

// 组件的 ref
this.ref = null;

// 待处理的 props
this.pendingProps = pendingProps;

// 已处理的 props
this.memoizedProps = null;

// 更新队列,用于存储组件更新的内容
this.updateQueue = null;

// 组件的 memoized state
this.memoizedState = null;

// 依赖关系,用于处理 useEffect 和 useMemo 的依赖项
this.dependencies = null;

// 当前 Fiber 节点的渲染模式
this.mode = mode;

// 表示 Fiber 节点上的副作用标记,用于处理生命周期、状态更新等副作用
this.effectTag = NoEffect; // 初始化为 NoEffect
this.nextEffect = null; // 指向下一个有副作用的 Fiber 节点

// 用于链表形式存储有副作用的 Fiber 节点
this.firstEffect = null; // 指向第一个有副作用的 Fiber 节点
this.lastEffect = null; // 指向最后一个有副作用的 Fiber 节点

// 表示当前 Fiber 节点的更新优先级
this.lanes = NoLanes;

// 表示当前 Fiber 节点的子级更新优先级
this.childLanes = NoLanes;

// 指向该 Fiber 在另一次更新时对应的 Fiber
this.alternate = null;
}
  • 静态数据结构的属性:

    • tag: 标记此 Fiber 节点的类型,比如 FunctionComponent、HostComponent 等。
    • key: 组件的 key。
    • elementType: 未使用,保留属性。
    • type: 组件类型或 DOM 元素类型。
    • stateNode: 表示此 Fiber 节点对应的组件实例或 DOM 节点。
  • 连接其他 Fiber 节点形成 Fiber 树的属性:

    • return: 指向父级 Fiber 节点。
    • child: 指向第一个子级 Fiber 节点。
    • sibling: 指向下一个兄弟 Fiber 节点。
    • index: 在父级的子级列表中的索引位置。
  • 动态的工作单元的属性:

    • pendingProps: 待处理的 props,即组件在调和过程中的 props。
    • memoizedProps: 已处理的 props。
    • updateQueue: 更新队列,用于存储组件更新的内容。
    • memoizedState: 组件的 memoized state。
    • dependencies: 依赖关系,用于处理 useEffect 和 useMemo 的依赖项。
    • mode: 当前 Fiber 节点的渲染模式。
  • 副作用标记和链表属性:

    • effectTag: 表示 Fiber 节点上的副作用标记,用于处理生命周期、状态更新等副作用。
    • nextEffect: 指向下一个有副作用的 Fiber 节点。
    • firstEffect: 指向第一个有副作用的 Fiber 节点。
    • lastEffect: 指向最后一个有副作用的 Fiber 节点。
  • 调度优先级相关的属性:

    • lanes: 表示当前 Fiber 节点的更新优先级。
    • childLanes: 表示当前 Fiber 节点的子级更新优先级。
  • 备份属性:

    • alternate: 指向该 Fiber 在另一次更新时对应的 Fiber。

Fiber链表

所有Fiber节点都以链表的形式链接在一起,形成称为Fiber树的数据结构。Fiber树代表了整个React组件层次结构,并且在Render阶段和Commit阶段的Fiber树之间进行切换。

在Render阶段,React遍历整个Fiber树并构建一份新的UI。在Commit阶段,React再次遍历整个Fiber树,并将新UI与旧UI进行比较并进行必要的DOM操作。

Fiber工作循环

当React需要更新组件树时,它会执行以下步骤:

  • 创建一个新的Fiber根节点。
  • 将当前的Fiber树作为旧树保存在新根节点中。
  • 调用组件的render方法,构建新的UI,并将其存储在新根节点中。
  • 遍历新根节点和旧树,比较它们之间的区别,并对DOM进行必要的更新。

React使用fiber对象来表示当前正在运行的任务。在工作循环中,React会不断地取出待处理的fiber对象并执行相应的操作。当一个fiber对象执行完毕时,它会将控制权交还给React引擎,并标记自己为“完成”。

React使用一个双缓冲机制来管理渲染过程。这意味着在切换Render阶段和Commit阶段时,React会将旧的Fiber树复制到新的根节点中,并在新根节点上重新构建一份UI。这样,React就可以在不中断用户界面的情况下准备并呈现出下一帧的UI。

双缓冲机制是什么

React 双缓冲机制是指在 React 渲染过程中使用两个缓冲区来交替处理渲染内容,从而提高性能和避免页面闪烁。

具体来说,React 双缓冲机制的工作原理如下:

  • 当前显示的缓冲区:页面上显示的内容被渲染到当前显示的缓冲区中。

  • 后台缓冲区:在渲染过程中,React 同时在后台维护一个另一个缓冲区。所有的 DOM 更新和变化都首先在后台缓冲区中进行,而不直接影响当前显示的缓冲区。

  • 内容交换:当后台缓冲区中的内容准备好并完成渲染时,React 将后台缓冲区的内容与当前显示的缓冲区进行交换。这样,当前显示的缓冲区成为后台缓冲区,后台缓冲区成为当前显示的缓冲区。

通过这种双缓冲机制,React 可以在后台缓冲区中进行所有的更新操作,而不会直接影响到用户当前正在看到的内容。这样可以避免页面闪烁和不必要的重绘,提高了用户体验。同时,在更新完成后,React 可以立即将新的内容呈现给用户,而不需要等待页面的重绘,也就是说,切换缓冲区的操作是原子性的,用户不会看到渲染过程的中间状态。

任务优先级

Fiber架构引入了一种新的调度算法来提高React应用程序的性能和响应能力。这个算法基于一系列优先级来确定哪些任务应该优先执行。React为每个任务分配一个优先级,并始终确保只有具有最高优先级的任务才会执行。

React Fiber 架构中定义了以下几个优先级的任务:

  • NoPriority (无优先级):

    表示没有明确的优先级,任务将以普通优先级处理。通常用于不需要特别优先级的任务,或者在没有明确指定优先级的情况下使用。

  • ImmediatePriority (立即优先级):

    表示立即执行的任务,具有最高的优先级。这些任务必须立即执行,不能被打断或延迟。通常用于处理紧急的用户交互、动画等任务。

  • UserBlockingPriority (用户阻塞优先级):

    表示用户阻塞级别的任务,优先级高于普通任务但低于立即执行任务。这些任务会在用户操作后尽快执行,以确保及时响应用户操作。

  • NormalPriority (普通优先级):

    表示普通级别的任务,通常用于一般性的更新操作。这些任务的优先级介于用户阻塞级别和低优先级之间。

  • LowPriority (低优先级):

    表示低优先级的任务,优先级较低,通常用于处理后台任务、预加载等不那么紧急的操作。

  • IdlePriority (空闲优先级):

    表示空闲时执行的任务,优先级最低,用于处理在系统空闲时执行的任务,以尽量减少对系统资源的占用。

React会根据任务的优先级和当前的上下文环境来动态地调整任务的执行顺序,以此来提高React应用程序的响应能力和性能。

源码位置

工作循环优先级

Fiber架构引入了一种新的工作循环模型来处理不同优先级的任务。在这个模型中,React使用一个由多个优先级队列组成的工作循环,每个队列都对应着一种特定的任务优先级。

当React需要执行某个任务时,它会将该任务添加到相应的优先级队列中。然后,React开始遍历整个队列,从中找出具有最高优先级的任务,并将其加入到待处理任务列表中。一旦所有高优先级任务都已经执行完毕,React才会继续处理低优先级任务。

源码位置

  • while (workInProgress !== null && !shouldYield()) { … }:

    这是一个 while 循环,其条件是 workInProgress 不为 null 且 shouldYield() 返回 false。workInProgress 表示当前正在进行工作的 Fiber 节点,当它不为 null 时说明还有任务需要执行。shouldYield() 是一个函数,用于检查是否需要暂停执行当前任务以便让出执行权给其他任务。

  • performUnitOfWork(workInProgress);:

在循环体中调用了 performUnitOfWork 函数,传入了当前的 workInProgress。这个函数的作用是执行当前工作单元(Fiber 节点)的任务。在 React Fiber 中,每个任务都是以 Fiber 节点的形式表示的,而 performUnitOfWork 函数负责执行这些任务,执行完成后会更新 Fiber 树的状态并返回下一个工作单元。

shouldYield

既然我们以浏览器是否有剩余时间作为任务中断的标准,那么我们需要一种机制,当浏览器有剩余时间时通知我们。

其实部分浏览器已经实现了这个 API,这就是requestIdleCallback。但是由于以下因素,React放弃使用:

  • 浏览器兼容性
  • 触发频率不稳定,受很多因素影响。比如当我们的浏览器切换 tab 后,之前 tab 注册的requestIdleCallback触发的频率会变得很低

基于以上原因,React实现了功能更完备的requestIdleCallbackpolyfill,这就是Scheduler。

performUnitOfWork

performUnitOfWork 方法是 React Fiber 架构中的一个核心函数,它负责执行当前工作单元(Fiber 节点)的任务,并返回下一个工作单元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function performUnitOfWork(workInProgress) {
// 执行当前工作单元的任务
const next = beginWork(workInProgress);

// 如果当前工作单元还有子节点,返回子节点作为下一个工作单元
if (next !== null) {
return next;
}

// 如果当前工作单元没有子节点,则继续寻找兄弟节点作为下一个工作单元
let sibling = workInProgress;
while (sibling !== null) {
// 寻找下一个兄弟节点
const returnFiber = sibling.return;
const nextSibling = findNextSiblingToMount(returnFiber, sibling);
if (nextSibling !== null) {
return nextSibling;
}
// 如果当前兄弟节点没有下一个兄弟节点,则返回其父节点
sibling = returnFiber;
}

// 如果父节点也没有兄弟节点,则返回 null,表示没有下一个工作单元了
return null;
}

【react】浅谈react fiber架构
https://www.cccccl.com/20240307/react/浅谈react fiber架构/
作者
Jeffrey
发布于
2024年3月7日
许可协议