【React】Fiber 实现可中断的渲染

2023-11-01

什么是可中断的“渲染”

参照我们在《Concurrent 的奥秘》中的同步渲染并发渲染的例子:

上图是同步渲染过程。

上图是并发渲染过程。

我们可以看到明显的区别:

  • 同步渲染:就是完整地执行了一个很耗时的渲染

  • 并发渲染:将原本耗时的 “渲染”,拆解成了很多个 “微任务” 去执行,同时可以在期间穿插执行其他更高优先级的 “渲染”

其实 React 的设计思路为:当我们更新应用状态的时候,可以赋予他们不同的优先级,React 就可以依据优先级顺序来进行 “渲染” 工作,而实现一种 “局部优先渲染” 的效果。

那要实现这个思路,很关键的一点是:高优先级的 “渲染” 就要被优先保证。

其中就包括:即便已经在进行低优先级的 “渲染” 了,也要可以中断它,来进行高优先级的 “渲染”

时间切片

再回看我们《Concurrent 的奥秘》并发渲染的例子:

低优先级的 “渲染” (绿色)是被拆成了很多 “微任务” 的方式在执行。期间,还会被不断穿插执行高优先级的 “渲染” (红色)。

其实对于低优先级的 “渲染” ,React 对其进行了时间限额(5毫秒)。“渲染” 时就需要不断地检查此次执行是否超时,如果一旦发现执行已经超过时间限额,则需要暂停,将剩余的 “渲染” 下次执行。这就被叫做 “时间切片”。

有关 “时间切片” 的内容,可以翻阅《调度系统 - Scheduler》

我们说到 “渲染” 任务需要不断检查自身的执行是否超时,那这个又是如何做到的呢?

下面,就从 “渲染” 的基本单元 - Fiber ,来探秘 “渲染” 过程到底是怎样的。

“渲染” 的基本单元 - Fiber

终于轮到 **Fiber** 登场了!想必大家看到过很多关于 **Fiber** 的解读了,这里再来简单赘述一下,**Fiber** 在我们 React 应用的生命周期中,到底扮演一个什么样的角色。

Fiber 就是 VDOM

通常,我们都使用 **JSX** 来进行编码,通过编译它被转化成了 React.createElement 这样可执行的JS代码。

在程序运行的时候,根据 ReactElement ,在 React 内部构建了 **Fiber树** 这样的结构,用来描述我们 UI ,最终 React 会将它们转化为浏览器中的真实 DOM 结构。

整个流程如下图:

大家经常提到的 **VDOM** ,在 React 18 中其实指的就是 **Fiber树**

下图举例说明我们的代码生成的 **Fiber树** 是什么样子的:

“渲染” 的两个阶段

所谓 “渲染” ,就是 React 根据应用产生的更新,重新遍历整颗 Fiber树,将其更新为最新,最后映射为实际的 DOM 节点。

整个过程,可以分为两个阶段:**Reconcile****Commit**

**Reconcile** 阶段:遍历 **Fiber树**,并将其更新。在源码中,大家可以去查看 **workLoopConcurrent** 这个函数。

**Commit** 阶段:将新的 Fiber树 映射为实际 DOM 的过程。在源码中,大家可以去查看 **commitRoot** 函数。

很明显的,如果 “渲染” 被 “中断” 的话,一定不能在 **Commit** 阶段被中断

因为这个阶段已经产生了实际 DOM 的更新,一旦产生 “中断” ,意味着会将一个 “半成品” 的 UI 呈现给用户,这是无法接受的。

所以,如果要 “中断” 我们的 “渲染” 的话,那就只能选择在 **Reconcile** 阶段,即在 Fiber树 更新的阶段中。

那 React 到底如何做呢,我们看下一部分的内容。

“渲染” 中断

在基本了解了 **Fiber** 结构后,我们来进一步探索:“渲染” 是如何依靠Fiber实现可中断的

Reconcile 阶段

在上文我们得出结论,“渲染” 过程如果要中断,只能选择在 Reconcile 阶段,因为这个阶段只涉及 Fiber 的变更,而没有产生实际 DOM 的变更。

Reconcile 又可以划分为两个阶段:beginWorkcompleteUnitOfWork:

beginWork

处理每个 Fiber 上更新,将结果同步在 **Fiber**memoizedPropsmemoziedState 上,并使用 Flags 来标记每个节点需要进行何种处理。

这个阶段是从顶到下地遍历 **Fiber** 结构,先处理自身,然后子节点 **child**,没有子节点处理兄弟节点 **sibling**

completeUnitOfWork

将每个 **Fiber** 的全部子节点的 **flags****subtreeFlags** 都标记在自己的 **subtreeFlags** 上。

这个阶段是从下至上进行回溯的,因为要读取子节点标记的原因,需要保证子节点先处理完成。

如上图可见 **Reconcile** 的整体流程,在每处理完成一个 Fiber 节点时,会检查时间片是否到时,如果到了,则会中断此次 “渲染”

等到下一次进来的时候,可以直接从 **workInProgress** 上次中断的 Fiber 节点开始处理即可

“渲染” 中断时的回退

“渲染” 过程被中断的话,还面临着另外一个问题:已经更新过的一部分 Fiber 节点该怎么办?

我们需要将它们回退到本次更新前的状态,才能保证新一次更新是正确的。

那你会怎么处理这个 “撤销” 的操作呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7lGera1-1652258045923)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df2c8459ea554f2da0b4929171e10155~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

Fiber 的 “撤销处理” 如上图所示。

**current** 指向的是当前被实际渲染为 DOM 的树,当进入 **Reconcile** 阶段的时候,会拷贝一颗新的 Fiber树,我们称之为 **workInProgress**

如果整个过程顺利完成,会 **current** 指向新生成的这颗 Fiber树

如果出现 “渲染” 要被 “中断” 的时候,它会直接放弃 **workInProgress** 的处理

下一次更新依然依靠 **current** 即可,因为刚才的更新并没有更改任何 **current** 上的信息。

为什么会有 Fiber

之前一直在先入为主的聊 Fiber 这个东西,但是我们一直没有说 Fiber 从何而来?

在 Fiber 结构之前,当 React 开始 “渲染” 时,也是从顶层节点开始,调用节点的更新方法。

只不过遍历全部节点是依靠在每个节点的更新方法中,触发下层节点的更新。就这样层层嵌套,形成了所谓的 Stack Reconcile

这样的 Function Call Stack 使得 React 无法在中间某个步骤暂停后,再接着从暂停地方开始继续执行。

关于这一点,在 React Fiber Architecture 中也有所提及:

It follows that rendering a React app is akin to calling a function whose body contains calls to other functions, and so on

至于 Fiber 的出现,就是为了解决这个问题:实现一种可控制的 Call Stack

Wouldn’t it be great if we could customize the behavior of the call stack to optimize for rendering UIs? Wouldn’t it be great if we could interrupt the call stack at will and manipulate stack frames manually?

That’s the purpose of React Fiber. Fiber is reimplementation of the stack, specialized for React components. You can think of a single fiber as a virtual stack frame.

所以,这也就是原有的设计不支持中断的原因,从而诞生了 Fiber

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【React】Fiber 实现可中断的渲染 的相关文章

  • Analytics.js:“auto”参数在跟踪对象创建方面有何作用?

    我刚刚切换到新的 Google Analytics 分析 通用分析 https support google com analytics answer 2790010 hl en GB平台 使用新的 javascript API 分析 js
  • 退出动画在下一个 js 帧器运动中不起作用?

    我在我的应用程序中使用 next js 在我的动画中使用成帧器运动 我可以设置介绍动画 但退出动画根本不起作用 我已将代码包装在动画效果下 但它没有执行任何操作 我在这里缺少什么 下面是我的示例代码
  • 如何识别当前打开的每个单独的浏览器窗口?

    如何使用 javascript 唯一地识别当前为所有主要浏览器打开的每个单独的浏览器窗口 让我解释一下我需要了解的内容 并考虑以下场景 我有 3 个当前打开的浏览器窗口 任何现代浏览器 即 Chrome Firefox 等 每个窗口都包含多
  • 如何将 CAD (DWG) 文件转换为 GeoJSON?

    我正在使用 OpenLayers 需要将 DWG 文件转换为 GeoJSON 格式 我怎样才能做到这一点 事实上 GDAL 拥有完成此任务所需的工具 ogr2ogr 是 GDAL 中包含的一个程序 可以转换多种格式 https gdal o
  • For 循环覆盖 HTML 中的文本

    我的数组称为myEmployees其中有 5 个名字 但当我运行代码时 它只打印出其中的 3 个 我相信这种情况正在发生 因为for脚本中的循环会覆盖它在 HTML 文档中编写的前一行 我怎样才能解决这个问题 年度公告板公告 恭喜泰勒 你在
  • 获得焦点时如何移动文本框视口?

    我有一个文本框 其中可能包含大于文本框大小的字符串 当我打字时 文本框 视口 总是移动以显示我输入的最后一个字符 例如 当您在 SO 问题中写下一个非常大的标题时 A 问题是 如果文本框失去焦点 当它再次聚焦时 视口总是设置在文本的开头 而
  • 如何在严格模式下设置元素样式属性?

    Given body document getElementsByTagName body 0 iframe document createElement iframe iframe src protocol settings script
  • .addClass 仅添加到无序列表中单击的项目符号

    我有一个简短的无序列表 其中有两个项目符号 我添加了一些 Javascript 这样当我单击项目符号时 它会向其中添加一个类 问题是 它将该类添加到所有现有的 li 中 而不仅仅是我单击的那个 这是 JSFiddle http jsfidd
  • 多个链接 dc.js 图表的 d3-tooltips

    我正在寻找修改 dc js 的开箱即用工具提示 似乎有一个解决方案使用d3 js 工具提示 https github com Caged d3 tip as in 这个问题 https stackoverflow com questions
  • React - 在单个文件中导入多个 svgs

    我有多个 svg 文件 我想将所有这些文件导入和导出到一个文件中 icons js import IconVideoOn from assets img icons video on svg import IconVideoOff from
  • 如何编写具有依赖状态的 redux 减速器

    我正在开发一个 React Redux 应用程序 它允许将 小部件 添加到页面并在 2D 空间中进行操作 要求可以同时选择和操作多个小部件 我当前状态树的简化版本如下所示 widgets widget 1 x 100 y 200 widge
  • 使用 javascript/jquery 检查 .css 样式表的名称

    我正在尝试为论坛制作一个小 chrome 扩展 但我只希望它在论坛的某个区域中工作 问题是我不能只做 matches subforum 因为该论坛中的线程无法通过 URL 区分它们所在的子论坛 subforum 有自己的 css 样式表 所
  • this 关键字是构造函数中的窗口对象

    好吧 所以我以为我理解了这一点 没有双关语的意思 但显然不是 var Constructor function var internalFunction function return this window this myMethod f
  • 无法在 .js 文件内命中断点

    我升级到 win 8 现在我在管理员权限下运行 VS 2012 并在 MVC 4 中创建一个网站 我在线检查了不同的解决方案 但没有一个有效 我的解决方案中有一个 javascript 文件 但是当我在其中放置断点时 在运行时我看到 没有为
  • 使用 webpack 保留输出目录中的文件夹结构

    我有以下文件夹结构 index html app index tsx 与 webpack 捆绑后 我希望有以下输出目录 并将bundle js注入到index html中 dist index html dist app bundle js
  • 重写node.js中其他模块中的函数

    我正在尝试在 Node js 应用程序中使用 nodeunit 存根函数 这是我正在尝试做的事情的简化版本 In lib file js var request require request var myFunc function inp
  • 在 Node.js 中封装 require 可以解决相对路径调用

    我正在尝试创建一个 require 包装器来加载依赖项 但我发现很难让它像原始的 require 函数一样工作 当路径是相对路径时 包装器无法解析为正确的路径 因为我的加载程序和调用程序文件不在同一文件夹中 这是一个简化的描述 index
  • Angular JS:当我们已经有了具有作用域的指令控制器时,指令的链接函数需要什么?

    我需要对范围和模板执行一些操作 看来我可以在以下任何一个中做到这一点link函数或controller函数 因为两者都可以访问该范围 什么时候我必须使用link功能而不是控制器 angular module myApp directive
  • 使用 JavaScript 自动提交表单

  • 如何在没有消息时隐藏 Bootstrap 警报框

    我用 Bootstrap 做了一个简单的警报框 如下所示 div class alertBox span class alert alert info bag session username span div When there is

随机推荐

  • ab常见问题汇总

    测试服务器ab 被测试服务器apache apache版本2 2 25 问题一 socket Too many open files 24 解决 在测试服务器操作 1 查看当前系统设置 open files n 1024为1024 root
  • FPGA 时序约束 二 :创建时钟和时钟不相关约束

    创建时钟是针对代码中主时钟而言 创建时钟之前需要知道代码中的主时钟都是什么 可以在综合以后 打开综合 然后在TCL 中输入命令 report clock networks name mynetwork 确定了主时钟 就可以对其创建时钟周期约
  • win 双机mysql集群_heartbeat双机 V3 CRM实现多台mysql集群

    一 系统环境 OS AS 5 3 i386 两服务器 db ha1 10 0 3 194 db ha2 10 0 3 195 VIP 10 0 3 111 资源名称 ha mysql1 ha mysql2 fs mysql1 fs mysq
  • 将字符串“I am a student.”逆序输出为“student. a am I”

    给到一串字符 I am a student 将其输出为 student a am I 整体思路为先将整个字符串逆序为 tneduts a ma I 然后再将每个单词进行逆序转换为 student a am I 那么我们只需先写出将整个字符串
  • 数组目标值target两个整数,并返回它们的数组下标

    1 题目背景 给定一个整数数组nums和一个整数目标值target 请你在该数组中找出和为目标值target的那两个整数 并返回它们的数组下标 你可以假设每种输入只会对应一个答案 但是 数组中同一个元素在答案里不能重复出现 你可以按任意顺序
  • String 在Java中的用法详解

    认识String类 和 String的使用 1 创建字符串 1 常见的构造String的方式 2 String的基本概念 2 字符串比较相等 3 字符 字节 字符串的转换 1 字符与字符串 2 字节与字符串 4 字符串常见操作 1 字符串比
  • Java多线程和操作系统多线程关系

    这篇文章要讨论的是Java编程中的多线程和操作系统中的多线程的区别问题 线程状态 首先两者的线程状态是一样的 创建 就绪 执行 阻塞 终止 其实这五个状态也是进程的状态 那么Java中的多线程 和 OS中的多线程的区别在哪里 我们先来看下O
  • 骑士宣言

    关于骑士 有很多的妙论 在中古世纪 骑士是最耀眼的战斗力 从中也延伸出了骑士的八大美德 历来很多玩家都把骑士当作最灿烂的职业 但大家却不曾想过骑士原本是一种象征意义的精神 我们来为这八大美德宣誓 谦卑 谦虚和永远将自己放在卑微的位置是骑士的
  • RabbitMQ(二):五种模式的简单介绍

    RabbitMQ 二 五种模式的简单介绍 一 Hello World 点对点 二 Work Queues 工作队列 三 Publish Subscribe 发布订阅 四 Routing 路由 五 Topics 话题 RabbitMQ 一 C
  • 学习笔记:SemanticStyleGAN 面向可控图像合成和编辑的组合生成先验学习

    CVPR 2022 SemanticStyleGAN Learning Compositional Generative Priors for Controllable Image Synthesis and Editing 面向可控图像合
  • MES生产管理系统如何与ERP系统集成

    MES生产管理系统和ERP企业管理系统是制造企业信息化的重要组成部分 它们在生产管理 资源计划和业务流程等方面发挥着重要作用 实现MES与ERP系统的集成 可以更好地优化企业生产流程 提高生产效率和降低成本 本文将探讨MES管理系统解决方案
  • 区块链技术开发路线

    背景陈述 已经对区块链领域的学习研究了一段时间 总体来说 前期主要是围绕bitcoin架构及其源码学习的 但对这个领域的技术开发还是不太熟悉 为了使自己对区块链领域有一个系统的学习和技术锤炼 特此总结了如下技术开发路线 来逐渐充实自己的区块
  • 育碧2k微软服务器,2K工作室更名-2K Games,育碧,全境封锁2 ——快科技(驱动之家旗下媒体)--科技改变未来...

    2K Games旗下的 硅谷工作室 Silicon Valley Studio 现更名为 第31工会 31st Union 之所以要提到这间工作室 盖因创始人Michael Condrey之前是动视 大锤工作室联合创始人 更早以前还是EA
  • C++(一)#pragma once用法

    C 一 pragma once用法 用法 C C 中 在使用预编译指令 include的时候 为了防止重复引用造成二义性 ifndef ifndef CODE BLOCK define CODE BLOCK code endif CODE
  • Python OSError: symbol cublasLtHSHMatmulAlgoInit, version libcublasLt.so.11 not defined的解决

    问题与排查 原报错信息如下 OSError opt conda lib python3 8 site packages nvidia cublas lib libcublas so 11 symbol cublasLtHSHMatmulAl
  • 动态创建class

    需要引入命名空间 using System CodeDom using System CodeDom Compiler using Microsoft CSharp using System Reflection public static
  • ASP.NET中文显示乱码之解决方法

    ASP NET很灵活 这归功于它采用文本文件方式的配置方式 另外的那种用页面标识符的方法应该是从ASP延续下来的 写ASP 程序时候碰到中文显示问题 运行后发现ASP 从数据库中读出来的中文全部变成了 解决办法 方法一 在config we
  • 程序员眼中的流量卡:需求、选择与使用

    标题 程序员眼中的流量卡 需求 选择与使用 一 流量卡的需求分析 作为程序员 我们深知数据流量在我们的工作中的重要性 无论是开发 测试还是部署 亦或是远程工作 都需要网络的支持 使用流量卡可以为我们提供一种灵活 便携的网络解决方案 让我们可
  • 如何用java POI在excel中画线_Java中使用POI在Excel单元格中画斜线—XLS格式

    Excel主要有xls和xlsx两种格式 两种格式对应的POI接口使用方法也不同 本节主要介绍一下 在xls格式的Excel单元格中如何画斜线 1 初始化HSSFWorkbook对象 初始化HSSFWorkbook对象 创建两行两列单元格
  • 【React】Fiber 实现可中断的渲染

    什么是可中断的 渲染 参照我们在 Concurrent 的奥秘 中的同步渲染和并发渲染的例子 上图是同步渲染过程 上图是并发渲染过程 我们可以看到明显的区别 同步渲染 就是完整地执行了一个很耗时的渲染 并发渲染 将原本耗时的 渲染 拆解成了