美文网首页
第二四章 添加交互-对一系列状态更新进行排队

第二四章 添加交互-对一系列状态更新进行排队

作者: 深圳都这么冷 | 来源:发表于2023-02-13 09:04 被阅读0次

对一系列状态更新进行排队

设置状态变量将使另一个渲染排队。 但有时您可能希望在排队下一次渲染之前对值执行多个操作。 为此,有助于了解 React 如何批处理状态更新。

你将学习

  • 什么是“ batching”以及 React 如何使用它来处理多个状态更新
  • 如何连续对同一个状态变量应用多个更新

React批量状态更新

您可能期望单击“+3”按钮会使计数器递增三次,因为它调用了 setNumber(number + 1) 三次:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

然而,你可能还记得上一节中,每个渲染器的状态值都是固定的,所以第一个渲染器的事件处理程序中的数字值始终为 0,无论你调用 setNumber(1) 多少次:

setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);

但这里还有另一个因素需要讨论。 在处理状态更新之前,React 会等到事件处理程序中的所有代码都已运行。 这就是为什么重新渲染只发生在所有这些 setNumber() 调用之后。

这可能会让您想起在餐厅点菜的服务员。 服务员不会一提到您的第一道菜就跑到厨房! 相反,他们让您完成订单,让您对其进行更改,甚至接受桌上其他人的订单。

这使您可以更新多个状态变量——甚至来自多个组件——而不会触发太多重新渲染。 但这也意味着在您的事件处理程序及其中的任何代码完成之前,UI 不会更新。 这种行为,也称为批处理,可以让你的 React 应用程序运行得更快。 它还避免处理令人困惑的“半成品”渲染,其中仅更新了一些变量。

React 不会对多个有意的事件进行批处理,例如点击——每个点击都是单独处理的。 请放心,React 仅在通常安全的情况下才进行批处理。 这确保了,例如,如果第一次单击按钮禁用了表单,则第二次单击不会再次提交它。

在下次渲染之前更新相同变量多次

这是一个不常见的用例,但如果您想在下一次渲染之前多次更新同一个状态变量,而不是像 setNumber(number + 1) 那样传递下一个状态值,您可以传递一个计算下一个状态的函数 基于队列中的前一个,如 setNumber(n => n + 1)。 这是一种告诉 React “用状态值做某事”而不是仅仅替换它的方法。

现在尝试增加计数器:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        setNumber(n => n + 1);
      }}>+3</button>
    </>
  )
}

这里,n => n + 1 被称为更新函数。 当您将它传递给状态设置器时:

  1. 在事件处理程序中的所有其他代码运行之后,React 将此函数排队等待处理。
  2. 在下一次渲染期间,React 遍历队列并为您提供最终的更新状态。
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);

以下是 React 在执行事件处理程序时如何处理这些代码行:

  1. setNumber(n => n + 1): n => n + 1 是一个函数。 React 将其添加到队列中。
  2. setNumber(n => n + 1): n => n + 1 是一个函数。 React 将其添加到队列中。
  3. setNumber(n => n + 1): n => n + 1 是一个函数。 React 将其添加到队列中。
    当您在下一次渲染期间调用 useState 时,React 会遍历队列。 之前的数字状态是 0,因此 React 将其作为 n 参数传递给第一个更新函数。 然后 React 将你之前的 updater 函数的返回值作为 n 传递给下一个 updater,依此类推:
排队更新 n 返回值
n => n + 1 0 0 + 1 = 1
n => n + 1 1 1 + 1 = 2
n => n + 1 2 2 + 1 = 3

React 存储 3 作为最终结果并从 useState 返回它。
这就是为什么在上面的示例中单击“+3”会正确地将值增加 3。

如果在替换状态后更新状态会发生什么

这个事件处理程序怎么样? 您认为下一次渲染中的数字是多少?

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
}}>
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setNumber(n => n + 1);
      }}>Increase the number</button>
    </>
  )
}

下面是这个事件处理程序告诉 React 要做的事情:

  1. setNumber(number + 5): number为0,所以setNumber(0 + 5)。 React 将“替换为 5”添加到其队列中。
  2. setNumber(n => n + 1): n => n + 1 是一个更新函数。 React 将该函数添加到它的队列中。
    在下一次渲染期间,React 遍历状态队列:
排队更新 n 返回值
“replace with 5” 0(未使用) 5
n => n + 1 5 5 + 1 = 6

React 存储 6 作为最终结果并从 useState 返回它。

你可能已经注意到 setState(x) 实际上像 setState(n => x) 一样工作,但是 n 没有被使用!

如果在更新状态后替换状态会发生什么

让我们再试一个例子。 您认为下一次渲染中的数字是多少?

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
  setNumber(42);
}}>
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setNumber(n => n + 1);
        setNumber(42);
      }}>Increase the number</button>
    </>
  )
}

以下是 React 在执行此事件处理程序时如何处理这些代码行:

  1. setNumber(number + 5): number为0,所以setNumber(0 + 5)。 React 将“替换为 5”添加到其队列中。
  2. setNumber(n => n + 1): n => n + 1 是一个更新函数。 React 将该函数添加到它的队列中。
  3. setNumber(42):React 将“替换为 42”添加到其队列中。

在下一次渲染期间,React 遍历状态队列:

排队更新 n 返回值
“replace with 5” 0(未使用) 5
n => n + 1 5 5 + 1 = 6
“replace with 42” 6(未使用) 42

然后 React 存储 42 作为最终结果并从 useState 返回它。

总而言之,您可以这样想传递给 setNumber 状态设置器的内容:

  • 更新函数(例如 n => n + 1)被添加到队列中。
  • 任何其他值(例如数字 5)都会将“替换为 5”添加到队列中,忽略已经排队的内容。

事件处理程序完成后,React 将触发重新渲染。 在重新渲染期间,React 将处理队列。 更新函数在渲染期间运行,因此更新函数必须是纯函数并且只返回结果。 不要尝试从它们内部设置状态或运行其他副作用。 在严格模式下,React 将运行每个更新程序函数两次(但丢弃第二次结果)以帮助您发现错误。

命名约定

通常用相应状态变量的首字母命名更新函数参数:

setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);

如果您喜欢更冗长的代码,另一个常见的约定是重复完整的状态变量名称,例如 setEnabled(enabled => !enabled),或使用前缀,例如 setEnabled(prevEnabled => !prevEnabled)。

回顾

  • 设置状态不会更改现有渲染中的变量,但它会请求一个新的渲染。
  • React 在事件处理程序完成运行后处理状态更新。 这称为批处理。
  • 要在一个事件中多次更新某个状态,可以使用 setNumber(n => n + 1) updater 函数。

相关文章

  • AdventureCreator学习笔记19:使用物品

    添加物品使用 在热点脚本上可以添加物品使用,并可以添加交互。 编辑物品交互 在ActonList可以使用进行物品消...

  • 使用容器和Provider实现全局状态与局部状态管理交互

    场景 在实际中存在全局状态需要与局部状态进行交互,例如用户登录成功后需要通知业务模块更新数据(比如后台上传位置,开...

  • VRTK---UI交互

    **与UI进行交互要给控制器添加的必备脚本有这些:VRTK_UIPointer脚本用来跟UI进行交互,VRTK_C...

  • 线程池状态迁移

    1. RUNNING 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。 状态...

  • 使用bugly热更新记录(一)

    现在市面上热更新方案很多,本篇尝试使用bugly方案,bugly对tinker进行封装并添加后台管理,使用更新方便...

  • HTC vive交互基础

    找到左手右手model:添加碰撞组件 物体添加碰撞组件和刚体 则可以进行碰撞交互 pickUp脚本挂载左手上: 输...

  • 育碧为《幽灵行动:Ghost Recon Breakpoint》

    “Ghost Recon Breakpoint”为添加AI队友,系统于7月15日进行更新。 在更新中,将添加三个新...

  • 2018-07-01

    运营就是采取一系列的手段对产品进行分析,洞察用户诉求,对产品进行推广,获得用户,维护用户,进一步去完善更新产品以满...

  • 学生信息管理系统

    用户交互 用户可以进行的操作 1. 添加,删除,修改,defaultTableModel 2. 查找(筛选),排序...

  • ComplexHeatmap绘制复杂热图

    pheatmap交互 最近ComplexHeatmap更新可以将pheatmap对象进行参数转换。新增的Compl...

网友评论

      本文标题:第二四章 添加交互-对一系列状态更新进行排队

      本文链接:https://www.haomeiwen.com/subject/utgxkdtx.html