React setState 回调在相同更新时运行多次?

huangapple go评论93阅读模式
英文:

React setState callback runs multiple times for the same update?

问题

我在更新状态时遇到了一个奇怪的问题,这个问题同时出现在 useEffect 和回调函数中。这是 React 18(自动批处理)且严格模式关闭的情况下。

这里有一个演示:CodeSandbox

function App() {
  const [state, setState] = useState([]);
  const [state2, setState2] = useState(0);

  useEffect(() => {
    if (state2) {
      console.log("effect");
      setState(s => {
        console.log("effect setState", s);
        return [...s, "effect"];
      });
    }
  }, [state2]);

  return (
    <input
      onMouseDown={() => {
        setState2(1);
      }}
      onFocus={() => {
        console.log("focus");
        setState(s => {
          console.log("focus setState", s);
          return [...s, "focus"];
        });
      }}
    />
  );
}

在单击输入框时,onMouseDownonFocus 之前运行。我期望它记录如下:

effect
focus
effect setState []
focus setState ['effect']

然而,它却记录如下:

effect
focus
focus setState []
effect setState []
focus setState ['effect']

为什么在 onFocussetState 回调会运行两次?我在 React 文档或 GitHub 上找不到关于这个行为的任何提及,我猜这与并发模式有关?这导致了难以在 Stack Overflow 问题中解释的微妙错误。

英文:

I came across a weird issue when updating a state in both useEffect and a callback. This is React 18 (automatic batching) with strict mode off.

Here's a demo: CodeSandbox

function App() {
  const [state, setState] = useState([]);
  const [state2, setState2] = useState(0);

  useEffect(() =&gt; {
    if (state2) {
      console.log(&quot;effect&quot;);
      setState(s =&gt; {
        console.log(&quot;effect setState&quot;, s);
        return [...s, &quot;effect&quot;];
      });
    }
  }, [state2]);

  return (
    &lt;input
      onMouseDown={() =&gt; {
        setState2(1);
      }}
      onFocus={() =&gt; {
        console.log(&quot;focus&quot;);
        setState(s =&gt; {
          console.log(&quot;focus setState&quot;, s);
          return [...s, &quot;focus&quot;];
        });
      }}
    /&gt;
  );
}

When clicking the input, onMouseDown runs before onFocus. I expect it to log:

effect
focus
effect setState []
focus setState [&#39;effect&#39;]

However, it's logging:

effect
focus
focus setState []
effect setState []
focus setState [&#39;effect&#39;]

Why is it running the setState callback twice in onFocus? I couldn't find any mention of this behavior in the React docs or Github, I'm assuming this is related to concurrent mode? This is causing subtle bugs that's hard to explain in a SO question.

答案1

得分: 1

这里是您的修改后的沙盒,现在包括当前的渲染迭代...

const render = React.useRef(0);
render.current++;

这会与performance.now()高分辨率时间戳一起记录在日志中。

console.log(render, performance.now(), "<message>", ...);

您可以在控制台中看到结果...

1 2971 "mousedown" 
2 2974 "effect" 1
2 2978 "focus" 
3 2978 "focus setState" []
4 2982 "effect setState" []
4 2982 "focus setState" (1) ["effect"]

关于批处理,这个提示在这个声明中提到:

React不会在多个有意义的事件之间批处理

我认为您所看到的情况与React使用严格模式时的情况_类似_...

在严格模式下,React将每个更新函数运行两次(但会丢弃第二个结果),以帮助您找出错误。

在这种情况下,不同事件执行得很接近(但不是同时进行),因此为了避免问题,React会丢弃过时渲染的状态更新(#3),然后重新运行批处理队列。

考虑到最终状态与您期望的状态相同,我不明白其中任何问题,尽管这确实是一个有趣的观察。

英文:

Here's your modified sandbox, now including the current render iteration...

const render = React.useRef(0);
render.current++;

which is logged along with a performance.now() high-resolution timestamp

console.log(render, performance.now(), &quot;&lt;message&gt;&quot;, ...);

You can see the results in the console...

1 2971 &quot;mousedown&quot; 
2 2974 &quot;effect&quot; 1
2 2978 &quot;focus&quot; 
3 2978 &quot;focus setState&quot; []
4 2982 &quot;effect setState&quot; []
4 2982 &quot;focus setState&quot; (1) [&quot;effect&quot;]

The clue is in this statement regarding batching

> React does not batch across multiple intentional events

I think what you're seeing is similar to when React uses strict mode...

> In Strict Mode, React will run each updater function twice (but discard the second result) to help you find mistakes.

In this case, the different events are executing close together (but not simultaneously) so in order to avoid issues, React is discarding the state update for a stale render (#3) and re-running the batch queue.

Given the end state is the same as the one you expect, I fail to see how any of this is a problem though it is certainly an interesting observation.

huangapple
  • 本文由 发表于 2023年6月22日 09:11:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76528070.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定