React hook 渲染额外的时间

2024-04-09

我的代码导致了意外数量的重新渲染。

function App() {    
    const [isOn, setIsOn] = useState(false)
    const [timer, setTimer] = useState(0)
    console.log('re-rendered', timer)

    useEffect(() => {
        let interval

        if (isOn) {
            interval = setInterval(() => setTimer(timer + 1), 1000)
        }

        return () => clearInterval(interval)
    }, [isOn])

    return (
      <div>
        {timer}
        {!isOn && (
          <button type="button" onClick={() => setIsOn(true)}>
            Start
          </button>
        )}

        {isOn && (
          <button type="button" onClick={() => setIsOn(false)}>
            Stop
          </button>
        )}
      </div>
    );
 }

请注意第 4 行的 console.log。我期望注销以下内容:

重新渲染 0

重新渲染 0

重新渲染1

第一个日志用于初始渲染。第二个日志用于当“isOn”状态通过按钮单击发生更改时重新渲染。第三条日志是当 setInterval 调用 setTimer 时,它会再次重新渲染。这是我实际得到的:

重新渲染 0

重新渲染 0

重新渲染1

重新渲染1

我不明白为什么会有第四条日志。这是它的 REPL 的链接:

https://codesandbox.io/s/kx393n58r7 https://codesandbox.io/s/kx393n58r7

***为了澄清,我知道解决方案是使用 setTimer(timer => timer + 1),但我想知道为什么上面的代码会导致第四次渲染。


当您调用返回的 setter 时,该函数会发生大部分情况useState is dispatchAction within ReactFiberHooks.js https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js(当前从第 1009 行开始)。

检查状态是否已更改(如果未更改,则可能会跳过重新渲染)的代码块当前包含以下条件:

if (
  fiber.expirationTime === NoWork &&
  (alternate === null || alternate.expirationTime === NoWork)
) {

我看到这个的假设是这个条件在第二次之后评估为错误setTimer称呼。为了验证这一点,我复制了开发 CDN React 文件并将一些控制台日志添加到dispatchAction功能:

function dispatchAction(fiber, queue, action) {
  !(numberOfReRenders < RE_RENDER_LIMIT) ? invariant(false, 'Too many re-renders. React limits the number of renders to prevent an infinite loop.') : void 0;

  {
    !(arguments.length <= 3) ? warning$1(false, "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().') : void 0;
  }
  console.log("dispatchAction1");
  var alternate = fiber.alternate;
  if (fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
    // 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.
    didScheduleRenderPhaseUpdate = true;
    var update = {
      expirationTime: renderExpirationTime,
      action: action,
      eagerReducer: null,
      eagerState: null,
      next: null
    };
    if (renderPhaseUpdates === null) {
      renderPhaseUpdates = new Map();
    }
    var firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
    if (firstRenderPhaseUpdate === undefined) {
      renderPhaseUpdates.set(queue, update);
    } else {
      // Append the update to the end of the list.
      var lastRenderPhaseUpdate = firstRenderPhaseUpdate;
      while (lastRenderPhaseUpdate.next !== null) {
        lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
      }
      lastRenderPhaseUpdate.next = update;
    }
  } else {
    flushPassiveEffects();

    console.log("dispatchAction2");
    var currentTime = requestCurrentTime();
    var _expirationTime = computeExpirationForFiber(currentTime, fiber);

    var _update2 = {
      expirationTime: _expirationTime,
      action: action,
      eagerReducer: null,
      eagerState: null,
      next: null
    };

    // Append the update to the end of the list.
    var _last = queue.last;
    if (_last === null) {
      // This is the first update. Create a circular list.
      _update2.next = _update2;
    } else {
      var first = _last.next;
      if (first !== null) {
        // Still circular.
        _update2.next = first;
      }
      _last.next = _update2;
    }
    queue.last = _update2;

    console.log("expiration: " + fiber.expirationTime);
    if (alternate) {
      console.log("alternate expiration: " + alternate.expirationTime);
    }
    if (fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork)) {
      console.log("dispatchAction3");

      // The queue is currently empty, which means we can eagerly compute the
      // next state before entering the render phase. If the new state is the
      // same as the current state, we may be able to bail out entirely.
      var _eagerReducer = queue.eagerReducer;
      if (_eagerReducer !== null) {
        var prevDispatcher = void 0;
        {
          prevDispatcher = ReactCurrentDispatcher$1.current;
          ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
        }
        try {
          var currentState = queue.eagerState;
          var _eagerState = _eagerReducer(currentState, action);
          // Stash the eagerly computed state, and the reducer used to compute
          // it, on the update object. If the reducer hasn't changed by the
          // time we enter the render phase, then the eager state can be used
          // without calling the reducer again.
          _update2.eagerReducer = _eagerReducer;
          _update2.eagerState = _eagerState;
          if (is(_eagerState, currentState)) {
            // Fast path. We can bail out without scheduling React to re-render.
            // It's still possible that we'll need to rebase this update later,
            // if the component re-renders for a different reason and by that
            // time the reducer has changed.
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        } finally {
          {
            ReactCurrentDispatcher$1.current = prevDispatcher;
          }
        }
      }
    }
    {
      if (shouldWarnForUnbatchedSetState === true) {
        warnIfNotCurrentlyBatchingInDev(fiber);
      }
    }
    scheduleWork(fiber, _expirationTime);
  }
}

为了清楚起见,这是控制台输出以及一些附加注释:

re-rendered 0 // initial render

dispatchAction1 // setIsOn
dispatchAction2
expiration: 0
dispatchAction3
re-rendered 0

dispatchAction1 // first call to setTimer
dispatchAction2
expiration: 1073741823
alternate expiration: 0
re-rendered 1

dispatchAction1 // second call to setTimer
dispatchAction2
expiration: 0
alternate expiration: 1073741823
re-rendered 1

dispatchAction1 // third and subsequent calls to setTimer all look like this
dispatchAction2
expiration: 0
alternate expiration: 0
dispatchAction3

NoWork其值为零。可以看到第一条日志fiber.expirationTime after setTimer具有非零值。在第二个日志中setTimer打电话,那个fiber.expirationTime已移至alternate.expirationTime仍然阻止状态比较,因此重新渲染将是无条件的。此后,双方fiber and alternate过期时间为 0(NoWork),然后进行状态比较并避免重新渲染。

React Fiber 架构的描述 https://github.com/acdlite/react-fiber-architecture是尝试理解其目的的一个很好的起点expirationTime.

源代码中与理解它最相关的部分是:

  • ReactFiberExpirationTime.js https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberExpirationTime.js
  • ReactFiberScheduler.js https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberScheduler.js

我相信过期时间主要与默认情况下尚未启用的并发模式相关。过期时间表示 React 将强制尽早提交工作的时间点。在此时间点之前,React 可能会选择批量更新。某些更新(例如来自用户交互的更新)具有非常短(高优先级)的过期时间,而其他更新(例如来自提取完成后的异步代码的更新)具有较长(低优先级)的过期时间。触发的更新setTimer从内部setInterval回调将属于低优先级类别,并且可能会被批量处理(如果启用了并发模式)。由于该工作有可能已被批处理或可能被丢弃,如果先前的更新有expirationTime.

你可以看看我的回答here https://stackoverflow.com/questions/53974865/how-do-react-hooks-determine-the-component-that-they-are-for/53980190#53980190详细了解如何通过 React 代码找到实现此目标的方法dispatchAction功能。

对于其他想要自己挖掘的人,这里有一个包含我修改后的 React 版本的 CodeSandbox:

React 文件是这些文件的修改副本:

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

React hook 渲染额外的时间 的相关文章

  • 有没有办法显示嵌套在 Grid 组件内的 Material-UI 抽屉?

    我正在使用 Material UI 创建一个 Web 应用程序 主页分为 3 个网格 每个网格有一个height of 500px 我想在中间网格内显示一个带有一些操作选项的抽屉 那可能吗 到目前为止 我只能在整个屏幕上显示它 这是我的主要
  • 了解 React Native 中的默认字体大小

    在过去的几个月里 我一直在开发一个 React Native 应用程序 但有些事情总是让我困惑 而我现在正试图弄清楚它的真相 我正在尝试标准化应用程序中的字体大小 正文 标题等 并且正在努力了解 React Native 究竟从哪里获取默认
  • 哪种反应钩子与 firestore onsnapshot 一起使用?

    我在我的 React Native 应用程序中使用了大量的 Firestore 快照 我也在使用 React hooks 代码看起来像这样 useEffect gt someFirestoreAPICall onSnapshot snaps
  • 发布到 npm 时出现问题

    所以我尝试使用 React 构建一个开源项目并将其推送到 npm 问题是 我的组件虽然在测试环境中运行良好 安装到其他组件 但当我将其发布到 npm 并下载包并尝试访问它时 它给了我一个错误 这是代码的一个小示例 import React
  • 移动浏览器中的 React 性能

    我有一个组件 表 其中包含许多行 其中包含数据编辑 其掩码形式为contenteditable 可以选择所有字段并同时更改所有行 在桌面上它运行得相当快 但在 iPhone 6 上我有不真实的滞后 Safari 每次操作都会挂起 20 秒
  • 过滤嵌套的 JSON 对象

    我有一个搜索栏 您可以在其中输入员工姓名 它应该根据过滤器返回姓名 我有一个嵌套的 JSON 对象 如下所示 我需要深入了解该对象以访问数组中的员工姓名 您可以看到我尝试实现的多个选项 它们已被注释掉 我的问题是代码没有过滤名称并返回所有名
  • 在React组件中使用的字符串变量中插入html

    我正在为我的投资组合网站构建一个反应应用程序 目前我已经用 JSX 编写了应用程序 因此我可以添加以下内容 class Project extends React Component render return div h1 this pr
  • 如何在ReactJS中定义常量

    我有一个将文本映射到字母的函数 sizeToLetterMap function return small square s large square q thumbnail t small 240 m small 320 n medium
  • 如何将 hls.js 与 React 结合使用

    我需要一些帮助来尝试弄清楚如何在 React 中使用 hls js 让我解释一下我必须从 api 获取 m3u8 的情况我能够使用基本的 html 使其工作
  • 如何在react-select v2中创建optgroup?

    我想在我的反应选择列表中包含 optgroups 但它似乎没有记录在任何地方 我有以下结构 是从评论中提取的https github com JedWatson react select issues 59 https github com
  • Ant设计文件上传中使用customRequest

    我正在使用 Axios 来处理文件上传 我遇到显示文件上传进度的问题 我使用的文件上传视图是 图片卡 HTML
  • React.js:可以在函数组件之外分配 useState() 吗?

    是否有任何语法允许分配useState在功能组件之外 到目前为止我已经尝试过 但没有成功 import React useState useEffect from react import ReactDOM from react dom f
  • React中如何触发同级组件的函数?

    I am new to front end world and could not figure out how to trigger a function from a sibling component Lets say I have
  • Next.js:错误:React.Children.only 期望接收单个 React 元素子元素

    我有一个名为Nav inside components目录及其代码如下所示 import Link from next link const Nav gt return div a Home a a About a div export d
  • 使用 React 渲染来自 Express 的 Flash 消息

    我已经搜索了很多 但一直无法找到一种简单的方法来从 Express 获取 Flash 消息并在 React 中渲染它们 我需要访问 Express 服务器上的数据 但是存储这些数据并将其传递给 React 的最佳方式是什么 我正在考虑传递一
  • 将渐变应用于 Material UI 的主题背景

    我试图将 MuiTheme 的默认背景颜色设置为渐变 我有以下代码 export const theme createMuiTheme palette type dark background default linear gradient
  • 如何在react-三纤维中提取并播放动画

    嗯 我有 gltf 动画模型 我成功加载模型 但无法播放嵌入的动画 我想知道是否可以以任何方式解决它 顺便说一句 我正在反应中工作 先感谢您 在这里您可以找到型号https drive google com file d 1ZVyklaQu
  • npm install -g expo-cli 失败并显示“EPERM:不允许操作,取消链接 '...\adb.exe'

    我在运行时收到错误 npm install g expo cli 我尝试以管理员身份重新安装节点模块 但出现相同的错误 环境 Windows 10 节点版本 10 15 3 NPM版本 6 9 0 我预计安装会发生 但出现了这样的错误 np
  • 在不同环境中运行的react.js redux生产构建中将环境变量渲染到浏览器

    React redux realworld io 应用程序的自述文件位于https github com gothinkster react redux realworld example app https github com goth
  • 从输入类型编号获取无效值

    我正在使用输入类型数字 当它无效时 我如何从中获取值 例如 使用类型编号并仅打印 e 这本身是无效的 我正在使用 React 但我认为这个问题非常普遍 onChange event console log event target valu

随机推荐

  • Unescape 或 html 解码

    我正在使用树枝 1 12 2 我的代码从代码隐藏生成一些元素 当使用最新版本的 twig 渲染这些元素时 它们会进行 html 编码 for item in files folders tr class td img src border
  • 为什么即使 use_transactional_fixtures = false after_commit 也没有运行

    rspec 中的事务装置会阻止调用 after commit 但即使我使用以下命令禁用它们 RSpec configure do config config use transactional fixtures false end The
  • 比较 Double 和 Int 的最佳方式是什么?

    以下 C 代码不起作用 int iValue 0 double dValue 0 0 bool isEqual iValue Equals dValue 那么问题来了 比较 Double 和 Int 的最佳方法是什么 您确实不能以天真的方式
  • 交互式闪亮全局日期选择器

    我正在一个没有良好日期选择器的环境中使用 R 并且我正在尝试使用 R 弹出一个日期选择器来填补空白 大多数 R 日期选择器都需要 GTK 等 UI 库 闪亮的没有 我想做的就是弹出一个日期选择器 让用户选择日期 然后结束闪亮的会话并将日期传
  • 如何检测 Java 系统属性是否已更改?

    我想知道系统属性何时更改 我在应用程序服务器中有一个应用程序 它以某种方式更改系统属性 System setProperty 我认为 我看了看 发现了不同的方法 JPDA https stackoverflow com questions
  • Collections.sort 方法抛出类 java.lang.Integer 无法转换为类 java.lang.Double 错误[重复]

    这个问题在这里已经有答案了 我从基于空手道的 API 自动化中的 JSON 响应获取一些运行时值 我将它们存储到 ArrayList 中 例如 def ArrayList Java type java util ArrayList def
  • 具有动态数据库连接的flask-sqlalchemy

    我有一个主数据库 其中存储每个客户端自己的数据库连接 因此每个客户端都使用 2 个数据库 主数据库和自己的数据库 必须确定其连接 对于每个 http 调用 我怎样才能使用flask sqlalchemy扩展来做到这一点 或者可能是 纯粹是s
  • Dart 中的“const”和“final”关键字有什么区别?

    两者有什么区别const and finalDart 中的关键字 dart 的网站上有一篇文章 解释得很好 https news dartlang org 2012 06 const static final oh my html Fina
  • MySQL数据类型仅存储月份和年份[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我正在编写一个 PHP 应用程序来存储STUDENTMySQL 关系数据库中的数据 我正在尝试找到最好的方法 数据类型来存储月份和年份而不需要日
  • WKHTMLTOPDF 不渲染 Base64 图像

    我有以下简单的 HTML 页面 div img src data image gif base64 R0lGODlhFwAPAKEAAP wAAAMzMzLi3tywAAAAAFwAPAAACQIyPqQjtD98RIVpJ66g3hgEY
  • 分配给模型类中的属性的值在视图/控制器类中消失

    在我的模型类发送变量之前stringToDisplay NSLog 告诉我它有一个值 但是当我尝试在我的 ViewController 中使用它时 我只是得到 null 对我做错了什么有什么想法吗 好消息是 在研究这个问题的过程中 我在理解
  • 让 youtube.com 在 iF​​rame 中加载

    无法获取 Youtube 的主页或任何其他带有 youtube com 前缀的 URL 以加载到 iFrame 中 有什么建议或见解吗 Code YouTube 不允许嵌入 仅有的http www youtube com embed htt
  • 如何使用 WSGI 实现 Flask 应用程序按路径调度?

    我想使用单个域作为多个 Flask 应用程序的暂存环境 这些应用程序最终将在自己的域上运行 就像是 example staging com app1 example staging com app2 example staging com
  • 如何创建表格颤动?

    我有三个列表 我必须生成表格 这些来自用户输入的列表 px 0 1 0 2 0 3 x 0 1 2 这个第三个列表是第一个第二个列表的乘法 Mutiply 0 0 2 0 6 我想生成表格 列表的长度来自用户输入所以 我如何生成表 简单的方
  • Logback-android:日志未写入文件

    尝试使用 logback android 重定向日志消息 以便可以将消息保存在文件中 但是 它没有保存到文件中 这是我的 logback xml 文件配置 它存储在src 主要 资产在我的 Android Studio 中
  • java 的 == 行为不一致

    考虑这段代码 class test public static void main String args test inst test new test int i1 2000 int i2 2000 int i3 2 int i4 2
  • 我无法删除我的 VPC

    我想删除我的一个 VPC 但当我尝试删除时 出现此错误 我们无法删除以下 VPC vpc 8737bde2 10 100 0 0 16 Khoi VPC vpc vpc 8737bde2 具有依赖关系 无法删除 服务 AmazonEC2 状
  • 日期时间比较忽略种类?

    DateTime d1 new DateTime 2015 1 1 0 0 0 DateTimeKind Utc DateTime d2 new DateTime 2015 1 1 0 0 0 DateTimeKind Local Cons
  • SqlException 因为子查询返回超过 1 个值

    我有以下 LINQ 查询 用于构造一个结构以填充到 JavaScript 网格库中 这与本示例无关 但我想我仍然会解释这一点 var output myObjects Select p gt new RowModel ID p LeadUI
  • React hook 渲染额外的时间

    我的代码导致了意外数量的重新渲染 function App const isOn setIsOn useState false const timer setTimer useState 0 console log re rendered