简体   繁体   English

如何在 setState 回调之外访问最新的 state?

[英]how can I access the latest state outside of the setState callback?

Because of the asynchronous nature of React setState and the newly introduced react concurrent mode after a lot of research, I don't know how I can access the guaranteed latest state in the following scenario or any other scenario.由于 React setState的异步特性以及经过大量研究后新引入的react 并发模式,我不知道在以下场景或任何其他场景中如何访问保证的最新 state。

An answer from a react expert especially from the React team is appreciated so much.非常感谢 React 专家的回答,尤其是来自 React 团队的回答。

Please remember it's a so simplified example and the real problem may occur in a sophisticated project full of state updates, event handlers, async code that changes state, ...请记住,这是一个非常简单的示例,真正的问题可能发生在一个复杂的项目中,该项目充满了 state 更新、事件处理程序、更改 state 的异步代码......

import { useCallback, useState } from "react";

const Example = ({ onIncrement }) => {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    onIncrement(count, count + 1);  // Is count guaranteed to be the latest state here due to including count in the useCallback dependency array?
    setCount((count) => count + 1);
  }, [count, onIncrement]);

  return (
    <>
      <span>{count}</span>
      <button onClick={increment}>increment</button>
    </>
  );
};

const Parent = () => (
  <Example
    onIncrement={(currentCount, incrementedCount) =>
      console.log(
        `count before incrementing: ${currentCount}, after increment: ${incrementedCount}`
      )
    }
  />
);

export default Parent;

You may say I could call onIncrement in the setCount callback, but due to the new react concurrent mode you can see that in future react updates the onIncrement may be called twice and output the result twice which is not the desired result.你可能会说我可以在setCount回调中调用onIncrement ,但是由于新的 react 并发模式,你可以看到在未来的 react 更新中, onIncrement可能会被调用两次,而 output 会调用两次结果,这不是预期的结果。

The commit phase is usually very fast, but rendering can be slow.提交阶段通常非常快,但渲染可能很慢。 For this reason, the upcoming concurrent mode (which is not enabled by default yet) breaks the rendering work into pieces, pausing and resuming the work to avoid blocking the browser.出于这个原因,即将到来的并发模式(默认情况下尚未启用)将渲染工作分成几部分,暂停和恢复工作以避免阻塞浏览器。 This means that React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption).这意味着 React 可能在提交之前多次调用渲染阶段生命周期,或者它可能在根本不提交的情况下调用它们(因为错误或更高优先级的中断)。

You can already ( React 17 strict mode ) see that onIncrement will be called twice in development mode and in the feature after React concurrent mode becomes the default, so it may be called twice this way in production.您已经可以( React 17 严格模式)看到onIncrement在开发模式中会被调用两次,并且在 React 并发模式成为默认模式后的特性中会被调用两次,因此在生产中可能会以这种方式调用两次。

Does including count in the useCallback dependencies array guaranty that count is the latest state value?是否在useCallback依赖项数组中包含 count 以保证 count 是最新的 state 值?

You may suggest calling the onIncrement in a useEffect hook but that way I will not have access to the previous state, which unlike this example may be impossible to recalculate.您可能建议在useEffect挂钩中调用onIncrement ,但这样我将无法访问以前的 state,与此示例不同,它可能无法重新计算。 (using a ref for storing the previous state is not a solution) (使用 ref 存储以前的 state 不是解决方案)

Another problem with using useEffect is that I don't know for sure that this event handler (onIncrement) is the cause of the effect or has state change in another handler or useEffect callback caused the effect.使用useEffect的另一个问题是,我不确定这个事件处理程序 (onIncrement) 是导致效果的原因,还是在另一个处理程序中 state 更改或useEffect回调导致了效果。 (storing an extra state or ref for detecting the cause is overkill) (存储一个额外的 state 或用于检测原因的参考是矫枉过正的)

Thank you!谢谢!

The source of my confusion was articles that said react may postpone state updates due to heavy workload and that you shouldn't rely on the state value outside of setState callback.我感到困惑的原因是文章说,由于工作量大,反应可能会推迟 state 更新,并且您不应该依赖 setState 回调之外的 state 值。 After a lot of research and according to this answer , now I know that react may batch state changes but it always keeps the order.经过大量研究并根据这个答案,现在我知道反应可能会批量 state 更改,但它始终保持顺序。

If I've understood well, React will invoke the render phase after each state change batch, like event handlers in my case, and for sure it will invoke render phases in order but may choose to postpone them or not to commit them in concurrent mode.如果我理解得很好,React 将在每个 state 更改批次之后调用渲染阶段,就像我的情况下的事件处理程序一样,它肯定会按顺序调用渲染阶段,但可以选择推迟它们或不以并发模式提交它们.

The other source of my confusion was the below quote from 'React docs' :我感到困惑的另一个原因是以下来自“React docs”的引用:

The commit phase is usually very fast, but rendering can be slow.提交阶段通常非常快,但渲染可能很慢。 For this reason, the upcoming concurrent mode (which is not enabled by default yet) breaks the rendering work into pieces, pausing and resuming the work to avoid blocking the browser.出于这个原因,即将到来的并发模式(默认情况下尚未启用)将渲染工作分成几部分,暂停和恢复工作以避免阻塞浏览器。 This means that React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption).这意味着 React 可能在提交之前多次调用渲染阶段生命周期,或者它可能在根本不提交的情况下调用它们(因为错误或更高优先级的中断)。

which I had misunderstood and I thought that React might choose to ignore invoking some render phase completely, but the reality is that react will invoke it but might choose not to commit it.我误解了,我认为 React 可能会选择完全忽略调用某些渲染阶段,但现实是 react 会调用它但可能选择不提交它。

So fortunately the fix for this is tiny:幸运的是,解决这个问题的方法很小:

const incrementHandler = () => {
  const newCount = count + 1;

  // Event handlers can have side effects!
  // Calling onIncrement here even has an added benefit:
  // If onIncrement also updates state, the updates will get batched by React — which is faster!
  onIncrement(count, newCount);

  // You can also use the simpler updater form of just passing the new value in this case.
  setCount(newCount);
};

The above code is the answer from Brian Vaughn replying to me rising an issue in React's github repo.上面的代码是Brian Vaughn在 React 的 github repo 中回复我的回答。 https://github.com/facebook/react/issues/20924 https://github.com/facebook/react/issues/20924

The other answer from Dan Abramov which is so clarifying: Dan Abramov的另一个回答非常明确:

I think one crucial nuance we're overlooking here is React will not actually do any of that.我认为我们在这里忽略的一个关键细微差别是 React 实际上不会做任何事情。 In particular, React does offer a guarantee that you will not get a render of the "next click" before the "previous click" has been flushed.特别是,React 确实提供了保证,在刷新“上一次点击”之前,您不会获得“下一次点击”的渲染。 This has already been a guarantee with Concurrent Mode, but we're making it even a stronger one — click events (and similar "intentional" user event like keyboard input) will always flush without interruption in a microtask.这已经是并发模式的保证,但我们正在使它变得更强大——点击事件(以及类似的“有意”用户事件,如键盘输入)将始终刷新而不会中断微任务。 In practice, this means events for clicks and other intentional events will never get "ignored" as in this hypothetical scenario we're discussing.在实践中,这意味着点击事件和其他故意事件永远不会像我们正在讨论的假设场景中那样被“忽略”。 We'll include this in the documentation when it's stable.当它稳定时,我们会将其包含在文档中。

Thank you Dan and Brian谢谢丹和布赖恩

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM