当我在函数体中设置状态时,为什么 React 会变成 Infinite?

2023-11-21

如果我们用相同的值设置状态组件不会重新渲染,但当我在函数体中设置状态时它不适用。

例如,如果我在按钮单击和单击按钮上设置相同的状态,则组件不会在按钮单击时重新渲染

function Test1() {
  const [name, setName] = useState("Shiva");
  const onButtonClick = () => {
    console.log("Clicked");
    setName("Shiva");
  };
  console.log("Redering");
  return (
    <div>
      <span>My name is {name}</span>
      <button onClick={onButtonClick}>Click Me</button>
    </div>
  );
}

但是,当我在 return 语句之前设置相同的状态时,React 会进行无限渲染

function Test2() {
  const [name, setName] = useState("Shiva");
  // ... come stuff
  setName("Shiva");
  console.log("Rendering");
  return (
    <div>
      <span>My name is {name}</span>
    </div>
  );
}

内部到底发生了什么?


React 根据您调用的位置使用不同的方法来安排更新setState.

例如,您的setState事件处理程序内部将使用

export function enqueueConcurrentHookUpdate<S, A>(
  fiber: Fiber,
  queue: HookQueue<S, A>,
  update: HookUpdate<S, A>,
  lane: Lane,
)

当你的setState在组件顶层使用

function enqueueRenderPhaseUpdate<S, A>(
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
)

你打电话时setState,React会内部调用一个函数,

function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
)

如果你检查这个函数的声明,你会发现一个顶级条件检查,

  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {}

Your setState事件处理程序内部将使用else阻止同时setState在渲染阶段(顶级函数体)将使用if block.


额外的

那么 React 如何决定是否处于渲染阶段呢?如果你检查代码isRenderPhaseUpdate你可以看到,

function isRenderPhaseUpdate(fiber: Fiber) {
  const alternate = fiber.alternate;
  return (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  );
}

现在你可能听说过虚拟 DOM,实际上它是一个链表。链表的每个对象称为纤程节点。这些 Fiber 节点只不过是普通的 javascript 对象。每个光纤节点都有一个名为的字段,alternate.

可以有 2 个独立的光纤树(虚拟 dom)。其中一棵 Fiber 树被称为当前树,它是提交给 DOM 的一棵树。另一种纤维树称为工作树。这是当状态更新发生但尚未提交到 DOM 时 React 新构建的一个。

因此,对于给定的组件,最多可以有两个 Fiber 节点(一个来自当前树,另一个来自 work-in-tree )。这两个光纤节点使用alternate field.

currentlyRenderingFiber是一个全局变量,它通过 React 跟踪当前渲染的 Fiber 节点。

现在你应该能够理解上面的正文了isRenderPhaseUpdate功能。

补充说明结束


案例 01 - 事件处理程序

当你触发setState从事件处理程序反应将使用else上述函数的块。

如果你检查身体else块你会发现以下代码片段,

const currentState: S = (queue.lastRenderedState: any);
  const eagerState = lastRenderedReducer(currentState, action);
  update.hasEagerState = true;
  update.eagerState = eagerState;
  if (is(eagerState, currentState)) { // <-- checks if the previous value is equal to current one.
     enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
     return; // <-- return early without call `enqueueConcurrentHookUpdate`
  }
  // never reaches here if the `eagerState` & `currentState` are same
  const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
  if (root !== null) {
    const eventTime = requestEventTime();
    scheduleUpdateOnFiber(root, fiber, lane, eventTime);
    entangleTransitionUpdate(root, queue, lane);
  }

正如你所看到的is(eagerState, currentState)true (这对于你的情况来说是正确的,因为两者eagerState & currentState保持该值"Shiva") React 将提前退出函数而不调用enqueueConcurrentHookUpdate。这就是为什么 React 不会再次重新渲染相同的值。

CASE 02 - 组件的顶层

你打电话时setState从组件的顶层开始,它将在 React 遍历组件树并调用组件时运行(由于 React 执行组件主体)

请注意,React 是调用您的组件的人。你只是定义它们。 React 调用您的组件并在遍历时获取输出,即渲染和构建新的 Fiber 树(又名工作中树)

现在如果你检查身体

function enqueueRenderPhaseUpdate<S, A>(
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
) {
  // This is a render phase update. Stash it in a lazily-created map of
  // queue -> linked list of updates. After this render pass, we'll restart
  // and apply the stashed updates on top of the work-in-progress hook.
  didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  const pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}

注意上面函数的代码注释。它创建一个循环链接列表,其中包含来自setState在你的组件顶层主体中。

根据代码注释,循环链表中的这些隐藏更新将应用于您最新的 Fiber 节点中的钩子

现在注意函数中的这一行enqueueRenderPhaseUpdate,

didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;

didScheduleRenderPhaseUpdateDuringThisPass是一个全局变量。该变量用于 while 循环,

do {
    didScheduleRenderPhaseUpdateDuringThisPass = false;

    if (numberOfReRenders >= RE_RENDER_LIMIT) {
      throw new Error(
        'Too many re-renders. React limits the number of renders to prevent ' +
          'an infinite loop.',
      );
    }
    numberOfReRenders += 1;

    // some other code

    children = Component(props, secondArg);
  } while (didScheduleRenderPhaseUpdateDuringThisPass);

每次执行组件主体时(Component(props, secondArg);在 while 循环中),你正在触发enqueueRenderPhaseUpdate因为setState在你的组件主体中设置didScheduleRenderPhaseUpdateDuringThisPass to true这会触发while再次循环调用Component again.

一旦循环执行了25次,React就会抛出错误。

您可以在以下位置找到这些功能:

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

当我在函数体中设置状态时,为什么 React 会变成 Infinite? 的相关文章

  • JS如何获取多维数组的最大深度?

    我有一个多维数组 我想知道它的最大深度 我发现了这个灵魂 但它不适用于对象数组 const getArrayDepth arr gt return Array isArray arr 1 Math max arr map getArrayD
  • Object.assign() - 奇怪的行为需要解释

    我有这个代码 function margeOptions options passedOptions options Object assign options passedOptions let passedOpts a true let
  • 将参数传递给 jquery 单击事件中的回调函数[重复]

    这个问题在这里已经有答案了 直接进入正题 我有一个 jquery 事件监听器 如下所示 number click printNumber 和一个回调函数 function printNumber number console log num
  • 将 FBX 文件转换为 .gltf 后,模型非常小,为什么?

    问题 将 FBX 文件转换为 gltf 后 模型非常小 为什么 我尝试用以下方法缩放模型frontObject scale set 1000 1000 1000 但我收到以下错误 TypeError Cannot read property
  • 使用 AntD 样式响应 Hook 表单

    我试图弄清楚如何将react hook form与antd前端一起使用 我已经制作了这个表单 它似乎正在工作 它是多部分表单向导的第 1 部分 只是不显示错误消息 谁能看到我在合并这两个表单系统时做错了什么 我没有收到任何错误 但我认为我已
  • IE8 中的 Javascript 消息超出堆栈空间

    我正在使用 Breeze 1 4 1 Internet Explorer 8 和 ASP NET MVC 4 Web API 我在查询时收到以下消息 查询失败 localhost port breeze Data Metadata 元数据导
  • Material UI 切换按钮 - 选择时无法更改背景颜色

    我正在尝试使用类似于单选按钮的 Material UI 切换按钮 为用户提供针对给定问题的 2 个选择 它的功能基本符合预期 但是当尝试调整选择每个切换按钮时的样式时 我无法更改切换按钮的背景颜色 我在 ToggleButton 组件上使用
  • Javascript 根据字段值任意排序数组

    所以我有一个对象数组 如下所示 var myArray priority low priority critical priority high 我需要以这种方式排序 1 关键 2 高和3 低 如何才能做到这一点 我建议使用一个对象来存储排
  • 如何正确关闭 Node.js Express 服务器?

    我需要在收到回调后关闭服务器 auth github callback网址 与平常一样HTTP API http nodejs org docs latest api http html关闭 服务器目前支持server close call
  • jQuery 问题:它的真正含义是什么?

    function window undefined jquery code jQuery window 它到底意味着什么 是不是也意味着 document ready 或者只是两种不同的东西 已经有两个答案 但这是我对代码缺失端的猜测 fu
  • 通过电子邮件发送在 HTML5 画布上创建的图像

    我有一个画布 用户可以通过交互来更改设计 现在 用户完成更改后 可以提交他的设计及其电子邮件 ID 但为了提交设计 我使用以下方法将画布转换为图像http www nihilogic dk labs canvas2image http ww
  • jQuery live() 和ready() 之间的区别?

    两者之间的确切区别是什么live and ready 编辑 发现die http docs jquery com Events die是相反的live ready http docs jquery com Events ready让你注册一
  • Jest - 语法错误:无法在模块外部使用 import 语句

    我在用jest 24 9 0无需任何配置 从 create react app 全局安装 在这些文件中我使用 es6 模块 使用时没有报错 test react scripts test 但是当我开始使用时jest with test je
  • Flot 0.8.2 折线图 - 颜色错误

    我正在使用 Flot 折线图并设置它们的颜色 我发现了一个奇怪的错误 在前 3 种颜色之后 绘图对所有其他线条使用最后一种颜色 这不是正确的行为 更有趣的是图例显示了正确的颜色 这是一个已知的错误 var dataSet label d1
  • 当元素具有多个类时如何在 switch 语句中检查 className

    在下面的示例中 我只想单击该选项以在警报中显示 我正在尝试使用 switch 语句来确定单击了哪个类 如果我的 div 不包含多个类 则我的示例将有效 我尝试使用classList contains在我的 switch 语句中无济于事 有没
  • 是否可以从 webpack 中的文件名中删除特殊字符?

    长话短说 我的资产文件名中不能包含某些字符 例如连字符 我没有运气通过解析 webpack 文档来弄清楚是否可以使用正则表达式或类似的东西重命名文件 这样我就可以从我无法控制源文件名的 3rd 方包中删除任何连字符 我的超级天真的例子是这样
  • jVectorMap - 向下钻取地图 - 自定义背景

    我正在使用 jVectorMap 中的向下钻取地图 并且尝试将自定义背景颜色设置为地图的第二层 为了自定义主级别 我使用 main 参数 但我不知道如何将其扩展到地图的较低级别 提前致谢 马切伊 None
  • Javascript 替换为正则表达式无法正常工作

    我正在尝试使用正则表达式验证名称 正则表达式阻止用户连续输入 2 个空格或点 这是我的代码 function test input var regex A Za z 0 1 s 0 1 input value input value rep
  • 当 jQuery .remove() 用于删除脚本标签时,它是否会清除加载的 JavaScript?

    正如标题所示 如果我使用以下命令从 DOM 中删除脚本标签 scriptid remove javascript 本身是保留在内存中还是被清除了 或者 我完全误解了浏览器处理 javascript 的方式吗 这是很有可能的 对于那些对我提问
  • 如何制作饼图聚合数据源?

    Using 适用于 ASP NET MVC 的 Kendo UI 完整版 http www kendoui com 版本 2013 3 1119 2013年11月20日 如果我有这段代码 status chart kendoChart da

随机推荐

  • C++ map——自引用迭代器

    有没有办法声明std map谁的值类型是其自身的迭代器 map
  • Chrome 开发者工具中 JavaScript 的奇怪行为

    Recently working with JavaScript in Developer Tool I found strange feature Chrome accepts any code between opening brack
  • 如何在 SwiftUI ScrollView 中设置可编辑 UITextView 的宽度?

    我正在构建一个 SwiftUI 应用程序 我想将一个可编辑的 UITextView 包装在 UIViewRepresentable 中 放入 SwiftUI ScrollView 中 原因是我有其他 SwiftUI 内容想要放在文本上方 并
  • 任何类型的指针都可以指向任何东西吗?

    这个说法正确吗 任何 TYPE 指针都可以指向任何其他类型吗 因为我相信如此 但仍然有疑问 为什么要为确定类型声明指针 例如 int or char 我能得到的唯一解释是 如果int类型指针指向一个char数组 那么当指针递增时 指针将从0
  • AVPlayer - 快退/快进流

    这是我在 viewDidLoad 中的代码 AVPlayerItem playerItem AVPlayerItem playerItemWithURL NSURL URLWithString http groove wavestreame
  • 是否可以有一个全局异常钩子?

    我的代码很好地涵盖了异常处理 try except 有些异常是预计不会发生的 有些异常经常发生 这是预料之中的 也是正常的 现在我想为这段代码添加一些自动化测试 最好知道执行期间发生了多少异常 这样我以后就可以查看是否引发了预期的数量或发生
  • 带有 Hibernate 后端的鉴别器、WrongClassException JPA

    我需要有一个抽象超类 我有 6 个该抽象超类的子类 我使用 JPA 中的 SINGLE TABLE 继承策略映射它们 在另一个 POJO 中 我与这些 1 6 类有一对多的关系 OneToMany mappedBy mSearchPrefe
  • HTML5 sessionStorage 可以写入磁盘吗?

    相关是否有安全的浏览器缓存 HTML5 会话存储的任何实现是否会写入磁盘 例如我可以依赖拥有一个 安全 私有 缓存吗 我知道它无法在浏览器会话之外持续存在 无论它是否 超时 已结束 注销 浏览器关闭 崩溃 计算机上的电源按钮 See HTM
  • GAE 数据存储备份

    是否有必要对GAE的数据存储进行备份 有谁有这样做的经验 建议和技巧吗 为了防止人为错误 始终需要进行备份 由于 App Engine 鼓励您构建针对同一数据集运行的代码的多个修订版本 因此能够返回非常重要 一个简单的转储 恢复工具在散装机
  • CALayer渲染上下文

    I use CATransform3D rotationAndPerspectiveTransform CATransform3DIdentity rotationAndPerspectiveTransform m34 1 0 500 成功
  • AWS Athena:删除日期范围之间的分区

    我有一个 athena 表 其基于日期的分区如下 20190218 我想删除去年创建的所有分区 我尝试了以下查询 但没有成功 ALTER TABLE tblname DROP PARTITION partition1 lt 20181231
  • 仅使用 CSS 的多色文本

    我不确定我的标题是否连贯地表达了我的问题 但我将在下面解释 我想为每个分配不同的颜色特点在文本字符串中使用only CSS 要查看我的问题的视觉效果以及进一步的解释 http codepen io Connor3xL pen ZOyzJK
  • php安装错误

    我已经使用 php 5 2 11 的插件版本安装了 WampServer2 1e x32 堆栈 我下载了apc3 0 19 dll并放在目录下 并在php ini中提供了所需信息 为了让它发挥作用 重新启动 wamp 服务器并导航到 php
  • 如何使用vbscript(同步)调用Web服务?

    其实例子有很多 我就用过其中之一 但它是异步工 作的 我的意思是它不会等待我调用的函数完成 function ProcessSend Set oXMLHTTP CreateObject MSXML2 XMLHTTP 4 0 Set oXML
  • 使用 javascript 获取我当前的地址

    我有兴趣使用 Javascript 获取我当前的地址 并通过组装其他一些 SO 线程来解决这个问题 1 2 所以想发布这个问题和答案 请参阅下面的答案 这是 HTML p p p p 这是JS var latitudeAndLongitud
  • Android/Java将String日期转换为long类型

    我需要将格式为 dd mm yyyy 的字符串转换为长类型 为了将值传递给android中的calendarProvider 目前我有 Calendar calendar Calendar getInstance long startEnd
  • Facebook PHP SDK - 图表返回错误:无效的 OAuth 访问令牌

    这是我的代码 登录 php
  • 按 WooCommerce 管理订单列表中的特定元字段过滤订单

    谁能告诉我 如何在 woo commerce 订单页面中按公司名称添加 设置过滤器 请分享功能或显示我的错误 以便我可以解决它 我尝试过但没有用 非常感谢您的帮助 add action restrict manage posts admin
  • TypeScript 泛型只会在简单情况下推断联合类型

    这是一个代码示例 declare function test ok
  • 当我在函数体中设置状态时,为什么 React 会变成 Infinite?

    如果我们用相同的值设置状态组件不会重新渲染 但当我在函数体中设置状态时它不适用 例如 如果我在按钮单击和单击按钮上设置相同的状态 则组件不会在按钮单击时重新渲染 function Test1 const name setName useSt