React - Fiber双缓冲

对fiber的理解

实际上,我们可以从三个维度来理解 Fiber

  • 是一种架构,称之为 Fiber 架构
  • 是一种数据类型
  • 动态的工作单元

是一种架构,称之为 Fiber 架构

在 React v16之前,使用的是 Stack Reconciler,因此那个时候的 React 架构被称之为 Stack 架构。从 React v16 开始,重构了整个架构,引入了 Fiber,因此新的架构也被称之为 Fiber 架构,Stack Reconciler 也变成了 Fiber Reconciler。各个FiberNode之间通过链表的形式串联起来:

js 复制代码
function FiberNode(tag, pendingProps, key, mode) {
    // ...
    // Fiber Node 通过链表的形式进行关联
    this.return = null;
    this.child = null;
    this sibling = null;
    this.index = 0;
    // ...
}

是一种数据类型

Fiber 本质上也是一个对象,是在之前 React元素基础上的一种升级版本。每个 FiberNode 对象里面会包含 React 元素的类型、周围链接的FiberNode以及 DOM 相关信息:

js 复制代码
function FiberNode(tag, pendingProps, key, mode) {
    // 类型
    this.tag = tag;
    this.key = key;
    this.elementType = null;
    this.type = null;
    this.stateNode = null; // 映射真实 DOM
    // ...
}

动态的工作单元

在每个 FiberNode 中,保存了本次更新中该 React元素变化的数据,还有就是要执行的工作(增、删、更新)以及副作用的信息:

js 复制代码
function FiberNode(tag, pendingProps, key, mode) {
    // ...
    // 副作用相关
    this.flags = NoFlags;
    this.subtreeFlags = NoFlags;
    this.deletions = null;
    // 与调度优先级有关
    this. lanes = NoLanes;
    this.childLanes = NoLanes;
    // ...
 }

为什么指向父 FiberNode 的字段叫做 return 而非 parent?

官方的日志提到过,因为作为一个动态的工作单元,return 指代的是 FiberNode 执行完 completeWork 后返回的下一个 FiberNode,这里会有一个返回的动作,因此通过 return 来指代父 FiberNode

Fiber 双缓冲

Fiber 架构同样用到了这个技术,在 Fiber 架构中,同时存在两棵 Fiber Tree,一棵是真实 UI 对应的Fiber Tree(current)(前缓冲区),另外一棵是在内存中构建的 FiberTree(workInProgress)(后缓冲区)

在React 源码中,很多方法都需要接收两棵 FiberTree:

js 复制代码
function cloneChildFibers(current, workInProgress){
// ...
}

current 指的就是前缓冲区的 FiberNode,workInProgress 指的就是后缓冲区的 FiberNode。 两个 FiberNode 会通过 alternate 属性相互指向:

js 复制代码
current.alternate = workInProgress;
workInProgress.alternate = current;

接下来我们从首次渲染(mount)和更新(update)这两个阶段来看一下 FiberTree 的形成以及双缓存机制:

mount 阶段

首先最顶层有一个 FiberNode,称之为 FiberRootNode,该 FiberNode 会有一些自己的任务:

  • Current Fiber Tree 与 Wip Fiber Tree 之间的切换
  • 应用中的过期时间
  • 应用的任务调度信息

现在假设有这么一个结构:

html 复制代码
<body>
    <div id="root"></div>
</body>
js 复制代码
function App(){
    const [num, add] = useState (0);
    return (
        <p onClick={() => add (num + 1) } > (num)</p>
    )
}

const rootElement = document.getElementById("root" );
ReactDOM.createRoot(rootElement).render (<App />);

当执行 ReactDOM.createRoot 的时候,会创建如下的结构:

此时会有一个 HostRootFiber, FiberRootNode 通过 current 来指向 HostRootFiber。

接下来进入到 mount 流程,该流程会基于每个 React 元素以深度优先的原则依次生成 wip FiberNode,并且每一个 wipFiberNode 会通过链表的形式连接起来,如下图所示:

生成的 wip FiberTree 里面的每一个 FiberNode 会和 current FiberTree 里面的FiberNode进行关联,关联的方式就是通过 alternate。但是目前 currentFiberTree里面只有一个 HostRootFiber,因此就只有这个 HostRootFiber 进行了 alternate 的关联。

当 wip FiberTree生成完毕后,也就意味着 render 阶段完毕了,此时 FiberRootNode就会被传递给 Renderer(渲染器),接下来就是进行渲染工作。渲染工作完毕后,浏览器中就显示了对应的 UI,此时 FiberRootNode.current 就会指向这棵 wip FiberTree,曾经的 wip FiberTree 它就会变成 current FiberTree,完成了双缓存的工作。

update 阶段

点击p元素,会触发更新,这一操作就会开启 update 流程,此时就会生成一棵新的 wip FiberTree,流程和之前是一样的。

新的 wip FiberTree 里面的每一个 FiberNode 和 current FiberTree 的每一个 FiberNode 通过 alternate 属性进行关联。

当 wip FiberTree 生成完毕后,就会经历和之前一样的流程,FiberRootNode 会被传递给 Renderer 进行渲染,此时宿主环境所渲染出来的真实 UI 对应的就是左边 wip FiberTree 所对应的 DOM 结构,FiberRootNode.current 就会指向左边这棵树,右边的树就在此成为了新的 wip FiberTree。

这个就是 Fiber 双缓存的工作原理。 另外值得一提的是,开发者是可以在一个页面创建多个应用的,比如:

js 复制代码
ReactDOM. createRoot (rootElement1) render (<Appl />);
ReactDOM. createRoot (rootElement2) .render (<App2 />);
ReactDOM. createRoot (rootElement3) • render (<App3 />);

在上面的代码中,我们创建了 3个应用,此时就会存在3个 FiberRootNode,以及对应最多6 棵 Fiber Tree 树。

总结

Fiber 可以从三个方面去理解:

  • FiberNode 作为一种架构:在 React v15 以及之前的版本中,Reconceiler 采用的是递归的方式,因此被称之为 Stack Reconciler,到了 React V16 版本之后,引入了 Fiber,Reconceiler 也从 Stack Reconciler 变为了 Fiber Reconceiler,各个FiberNode 之间通过链表的形式串联了起来。
  • FiberNode 作为一种数据类型:Fiber 本质上也是一个对象,是之前虚拟 DOM 对象(React 元素,createElement 的返回值)的一种升级版本,每个 Fiber 对象里面会包含 React 元素的类型,周围链接的 FiberNode,DOM 相关信息。
  • FiberNode 作为动态的工作单元:在每个 FiberNode 中,保存了"本次更新中该 React元素变化的数据、要执行的工作(增、删、改、更新Ref、副作用等)"等信息。

所谓Fiber 双缓冲树,指的是内存中构建两棵树,并直接在内存中进行替换的技术。在 React 中使用Wip FiberTree 和 Current FiberTree 这两棵树来实现更新的逻辑。Wip FiberTree 在内存中完成更新,而 Current Fiber Tree 是最终要渲染的树,两棵树通过alternate 指针相互指向,这样在下一次渲染的时候,直接复用 Wip FiberTree 作为下一次的渲染树,而上一次的渲染树又作为新的Wip FiberTree,这样可以加快 DOM 节点的替换与更新。

结语

如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。

文章如有错误之处,希望在评论区指正🙏🙏

相关推荐
AI大模型系统化学习10 小时前
AI产品风向标:从「工具属性」到「认知引擎」的架构跃迁
大数据·人工智能·ai·架构·大模型·ai大模型·大模型学习
stormsha10 小时前
MCP架构全解析:从核心原理到企业级实践
服务器·c++·架构
10000hours10 小时前
【存储基础】NUMA架构
java·开发语言·架构
nbsaas-boot13 小时前
商品模块中的多规格设计:实现方式与电商/ERP系统的架构对比
架构
想用offer打牌13 小时前
一站式了解BASE理论
后端·面试·架构
霸王蟹13 小时前
从前端工程化角度解析 Vite 打包策略:为何选择 Rollup 而非 esbuild。
前端·笔记·学习·react.js·vue·rollup·vite
EndingCoder14 小时前
React从基础入门到高级实战:React 生态与工具 - 构建与部署
前端·javascript·react.js·前端框架·ecmascript
市民中心的蟋蟀14 小时前
第十章 案例 4 - React Tracked 【上】
前端·javascript·react.js
Kookoos14 小时前
健康检查:在 .NET 微服务模板中优雅配置 Health Checks
微服务·架构·.net·abp vnext
工呈士14 小时前
React Hooks 与异步数据管理
前端·react.js·面试