🍂落页
登 录

React 18 笔记

  • Fiber 架构
  • 新 Suspense 下的 SSR 框架
  • API - react
  • API - react-dom
  • 跳出机制
  • 个人对 React 的理解
  • 发布博客
🍂落页
TALAXY
Fiber 架构
🍂落页
TALAXY

Fiber 架构

Fiber 架构
  • 回顾
  • Fiber 是什么

原文:React Fiber Architecture

回顾

什么是协调

  • 协调 reconciliation - React 比对树之间差异的算法,用来决定哪些部分需要更新。
  • 更新 update - 通过数据的更新来重新渲染,通常由 setState 触发。

React API 的核心概念是将更新视为整个应用的重新渲染。这让开发者仅需维护好状态的变化,而不用考虑如何从一个状态转变到另一个状态。

当状态更新时,如果重新渲染整个应用,通常代价是非常昂贵的。而 React 有优化手段,仅去执行部分必要的渲染工作。这些优化的大部分都来自于 协调 reconciliation 的过程之中。

协调也常被理解为「虚拟 DOM」。具体一点的解释如下:当你在渲染一个 React 应用时,背后会建立一棵节点树并存在内存中。之后这棵树会同步给渲染环境 - 比如对于一个浏览器应用,它会被转为一组 DOM 操作。当应用更新时(通常通过 setState),会生成一颗新树,这颗新树会与先前的树对比,来计算出需要执行哪些 DOM 操作来更新已渲染的应用。

尽管 Fiber 是对 reconciler 的彻底重写,但是 算法 大致是一致的。其中有这俩关键点:

  • 对于不同的组件类型,React 会直接对整个旧树进行替换。
  • 通过 key 来比对列表元素。key 应当是「稳定的、可预测的、唯一的」。

协调 vs 渲染

DOM 只是 React 的渲染环境的其中一种,还有 iOS 和 Android ,它们通过 React Native 渲染。(因此「虚拟 DOM」其实是个不恰当的用词)

因为协调和渲染是两个阶段,所以 React 能支持许多渲染目标。协调器会计算树的哪些部分需要更新;渲染器则通过这些信息来实际更新已渲染的应用。

这也意味着 React DOM 和 React Native 更共用同一个协调器,由 React core 提供。而 Fiber 就是这么个协调器。

调度

  • 调度 scheduling - 决定工作应当何时执行。
  • 工作 work - 计算工作。通常是指更新工作。

React 的 设计理念 对调度做了描述:

在 React 当前的实现中,React 在单个 tick 周期中递归地走完这棵树,然后调用整个更新后树的渲染方法。但是以后 React 可能会延迟一些更新操作来防止掉帧。

这在 React 的设计中很常见。有一些流行的库实现了 “push” 模式,即当新数据到达时再计算。然而 React 坚持 “pull” 模式,即计算可以延迟到必要时再执行。

React 不是一个常规的数据处理库,它是开发用户界面的库。我们认为 React 在一个应用中的位置很独特,它知道当前哪些计算当前是相关的,哪些不是。

如果不在当前屏幕,我们可以延迟执行相关逻辑。如果数据数据到达的速度快过帧速,我们可以合并、批量更新。我们优先执行用户交互(例如按钮点击形成的动画)的工作,延后执行相对不那么重要的后台工作(例如渲染刚从网络上下载的新内容),从而避免掉帧。

关键点如下:

  • UI 中不是所有的更新都需要及时地去执行,这么做可能会导致掉帧,影响用户体验。
  • 不同类型的更新有不同的优先级,比如动画更新应该比数据更新来得快。
  • 开发者不需要处理调度工作,React 会聪明地处理调度。

先前的 React 还没有充分利用调度机制,而这也是 Fiber 的主要目标之一。

Fiber 是什么

Fiber 需支持:

  • 暂停工作并在后续继续;
  • 为不同类型的工作设置优先级;
  • 重用先前已完成的工作;
  • 如果工作不再需要了则抛弃工作。

为了实现这些功能,我们需要先讲工作分解为小的单元。Fiber 因此得名,一个 fiber 代表一个小的任务单元。

To go further, let's go back to the conception of React components as functions of data, commonly expressed as

v = f(d)

It follows that rendering a React app is akin to calling a function whose body contains calls to other functions, and so on. This analogy is useful when thinking about fibers.

在计算机中使用 调用栈 来追踪程序的执行过程。当一个函数被调用时,会向调用栈中添加一个新的栈帧。栈帧代表着函数的执行工作。

而对于 UI ,如果一次(函数调用)执行了过多的工作,则会导致掉帧/卡顿等问题。而且有的工作可能会因为新的状态更新而变得没有意义,但依然在执行中,阻塞了新的工作的执行。

目前浏览器提供了一些方法来解决这些问题:requestIdleCallback 能够将一个函数(通常是低优先级)调度到闲置 idle 阶段去调用,而 requestAnimationFrame 会将函数(通常是高优先级)调度到下一个动画桢里调用。但如果要使用这些方法,你就需要将渲染工作(根据优先级)进行拆分。

而 React Fiber 专为 React 组件重新“实现”了调用栈。可以将一个 fiber 视为一个虚拟的栈帧。Fiber 可以根据需要控制虚拟栈帧的执行时间和执行方式,以此来实现调度。除了调度,Fiber 还能带来 并发、错误边界 等特性。

Fiber 的结构

一个 fiber 是一个 JS 对象。它包含了一个组件自身,以及其输入输出的信息。

一个 fiber 会关联一个栈帧,和一个组件实例。

以下是 fiber 的重要字段:

type 和 key

The type and key of a fiber serve the same purpose as they do for React elements. (In fact, when a fiber is created from an element, these two fields are copied over directly.)

type 表明一个 fiber 所对应的组件类型。对于自定义组件,type 则为组件函数或类组件自身,对于内置组件,比如 div 、span 等,type 则为一个字符串。从概念上讲,type 指的就是 v = f(d) 里的 f() 。

在 type 的基础上,key 则是用来决定该 fiber 是否能被重用。

child 和 sibling

这俩字段用于指向其他的 fiber ,用于描述一颗 fiber 树。

child 会与组件的 render 结果关联,比如对于下面这个例子中,Parent 的 child fiber 会与 Child 关联:

function Parent() {
  return <Child />
}

当返回多个子组件时,sibiling 字段则派上用场:

function Parent() {
  return [<Child1 />, <Child2 />]
}

子组件对应的 fiber 们会形成一个单链表。比如上面这个例子中,Child1 是头节点,并且会成为 Parent 的 child 。同时 Child1 的 sibling 是 Child2 。

回到 v = f(d) 这一公式,可以把子 fiber 理解为尾调用函数。

return

return 代表当前 fiber 执行后应当返回的 fiber 。通常可以认为是当前 fiber 的父亲 fiber 。

pendingProps and memoizedProps

概念上讲,props 即函数的参数。fiber 会在执行之前设置好 pendingProps ,并且在执行后存储到 memoizedProps 。

当来临的 pendingProps 与 memoizedProps 一致时,则会让 fiber 直接复用上一次的执行结果。

pendingWorkPriority

pendingWorkPriority 表明了一个 fiber 工作的优先级。ReactPriorityLevel 列出了所有优先级类型。除了 0 代表 NoWork 以外,越小的数字代表越高的优先级。

调度器会通过这个优先级字段来查找下一个要执行的工作。

alternate

  • flush - 通过 fiber 的结果来渲染到屏幕上。
  • work-in-progress - 指 fiber 正在执行中,仍未返回结果。

在任何时候,一个组件最多会关联两个 fiber :当前的已被用来渲染的 fiber ,和一个正在执行的 fiber 。而这两个 fiber 互为对方的 alternate 。

一个 fiber 的 alternate 会通过 cloneFiber 懒创建,而非均创建一个新对象。cloneFiber 会尝试重复使用 fiber 的 alternate(如果存在的话),以减少内存开销。

output

  • host component - React 应用的叶子节点。

每一个 fiber 都会有输出 output ,但是 output 只会在叶子节点上创建,并且会沿着树向上传递。

渲染器会使用 output 来获取变更,依次来进行渲染工作。

TALAXY 落于 2024年5月10日 。