【问题标题】:Detect if state updates are batched in React Hooks?检测是否在 React Hooks 中批量更新状态?
【发布时间】:2021-03-29 21:18:03
【问题描述】:

this question 中所述,React 在 React 事件处理程序中批量状态更新调用。

如果从基于 React 的事件中触发状态更新,例如按钮单击或输入更改,React 当前将批量更新状态。如果它们是在 React 事件处理程序之外触发的,它不会批量更新,比如异步调用。

即,当从 React 事件处理程序回调中调用时,setFoosetBar 将融合在一起。

我正在处理各种第三方库,其中一些包装了非 React JS 库。我正在向它们传递回调,我想知道这些回调是否属于“React 事件处理程序”类别,即状态更新批处理是否适用于它们。

有没有办法手动检查状态更新是否是批处理的,或者等效地,是否在 React 事件处理程序中执行了某些代码?我正在考虑进行临时检查,例如:

let callback = () => {
  // fictitious checks
  console.log(ReactDOM.checkIfUpdatesAreBatched());
  console.log(ReactDOM.checkIfRunningInReactEventHandler());
  setFoo("foo");
  setBar("bar");
}

编辑:我不想知道这一点的原因是为了确保FooBar 更新是原子的,以保持一致性。当然我可以将状态融合在一起,但这会带来性能问题,因为setFooAndBar 会触发太多组件重新渲染,这当然可以通过进一步的记忆来缓解......如果我能证明setFoo 和@ 987654330@ 始终使用更新批处理运行,状态分离是合理的。

【问题讨论】:

标签: reactjs react-hooks


【解决方案1】:

有没有办法手动检查状态更新是否是批处理的,或者等效地,是否在 React 事件处理程序中执行了某些代码?

只需在render函数(函数组件体)内记录,当批处理发生时,您将得到一个日志而不是两个。

确保只有此事件会导致重新渲染。


请注意,即使在非 React 事件中进行批处理的常见解决方法是将状态移动到单个对象中。您也可以使用另一个useEffect 并从中派生其他状态。

完整示例:

function Component() {
  const [a, setA] = useState("a");
  const [b, setB] = useState("b");

  console.log({ a });
  console.log({ b });

  const onClick = () => {
    setA("aa");
    setB("bb");
  };

  const onClickPromise = () => {
    Promise.resolve().then(() => {
      setA("aa");
      setB("bb");
    });
  };

  const [batch, setBatch] = useState({ a, b });

  const onClickPromiseBatched = () => {
    Promise.resolve().then(() => {
      setBatch({ a: "aa", b: "bb" });
    });
  };

  useEffect(() => {
    // Will get another render for deriving the state
    // Or just use only the batched state
    setA(batch.a);
    setB(batch.b);
  }, [batch]);

  return (
    <>
      <button onClick={onClick}>Render</button>
      <button onClick={onClickPromise}>Render with promise</button>
      <button onClick={onClickPromiseBatched}>
        Render with promise batched
      </button>
    </>
  );
}

【讨论】:

  • 你是对的,渲染计数可能是唯一的选择。也许我什至可以编写回归测试来保证单次重新渲染等。请注意,您的意思可能相反:当批处理发生时,您将得到一个日志而不是两个。
【解决方案2】:

没有办法做到这一点。你也不会真的需要这个。如果您的功能依赖于具有特定值的状态片段,请使用 useEffect 和依赖数组中的适当状态片段,并在您提供 useEffect 的回调中有条件地检查值。

如果您有一个非常具体的用例,实际上不需要批处理(并且您确定需要选择退出,99.999999% 的人不这样做),那么存在

ReactDOM.flushSync(() => {
  // this setState won't be batched
  setState(something)
})

这并不严格相关,但 React 团队的 Dan Abermov 与用户讨论了关于输入聚焦的批量更新:https://github.com/facebook/react/issues/18402

【讨论】:

  • 您实际上也不需要这个。出于一致性原因,我需要确保 FooBar 更新是原子的。当然我可以将状态融合在一起,但这有性能问题。如果我能证明setFoosetBar 总是使用更新批处理运行,那么状态分离将是合理的。我只是在寻找一种替代方法来阅读相关第三方库的整个代码,以确定它们是否真的总是从 React 事件处理程序中调用我。
  • 看看我发布的 GitHub 问题。关于能够进行这些原子调用有很多来回。没有办法知道状态调用是否会被批处理。根据 Dan 在该讨论中提供的信息,您可能能够阻止对您的调用进行批处理或隔离您的调用,从而使批处理无关紧要。
猜你喜欢
  • 2021-08-16
  • 1970-01-01
  • 2019-08-08
  • 1970-01-01
  • 2023-03-16
  • 2019-07-21
  • 2020-12-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多