React - useState 钩子第一次和后续设置状态时的奇怪行为

Posted

技术标签:

【中文标题】React - useState 钩子第一次和后续设置状态时的奇怪行为【英文标题】:React - weird behavior when useState hook sets state for the first time vs subsequent times 【发布时间】:2020-08-25 20:39:46 【问题描述】:

考虑这段代码:

const useState = React;

function App() 
  const [count, setCount] = useState(0);

  const onClick = () => 
    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    console.log("onclick");
  ;

  console.log("rendering");

  return <button onClick=onClick> Increment count </button>;


ReactDOM.render(<App/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

当运行并单击按钮时,这是它产生的输出:

1
onclick 
2
3
rendering 

我本来希望输出是

onclick 
1
2
3
rendering

因为我正在使用更新程序功能来访问以前的统计信息,所以默认情况下状态更新是批处理和异步的。

正如预期的那样,进一步点击按钮确认,并产生以下输出:

onclick 
4
5
6
rendering

我怀疑第一个设置状态在挂钩的情况下总是同步的,因为如果我将代码更改为:

function App() 
  const [count, setCount] = useState(0);

  const onClick = () => 
    // add this extra set state before any other state updates
    setCount(1);
    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    console.log("onclick");
  ;

  console.log("rendering");

  return <button onClick=onClick> Increment count </button>;

预期的输出是:

onclick 
2
3
4
rendering 

我还没有在网上找到任何解释。 这不会像我看到的那样影响任何功能,因为虽然第一次同步执行它仍然异步更新状态,这意味着我无法访问 console.log("onclick" + count) 中的更新状态

有助于解释为什么它会这样工作。

注意: 在 github 上对此进行了讨论。似乎这是我们作为消费者不应该关心的事情之一。这是一个实现细节。 https://github.com/facebook/react/issues/19697

【问题讨论】:

@Alexander Nied 我没有直接编辑这个问题,因为我没有看到很多人使用带有 React 的 SO sn-ps,而且我不想被打扰。 好问题。这与 React 如何将 setter 调用捆绑在一起有关,但我不能说更多。 @gaurav 你可以为这个问题开始赏金。我也想知道这种奇怪的行为。 【参考方案1】:

React 会将所有状态更改合并到一个更新中以减少渲染,因为渲染成本很高,setState 在调用 log 时不一定会更新。所以你可以只使用log("count="+(count+3)) 或使用useEffect

useEffect 在 state/prop 改变时设置回调;以下 sn-p 显示了如何在每次更改时记录 count。您可以参考the docs了解更多信息。

const useState,useEffect = React;

function App() 
  const [count, setCount] = useState(0);

  useEffect(()=>
    console.log("effect!count=" + count)
  ,[count])

  const onClick = () => 
    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    setCount((prevCount) => 
      console.log(prevCount + 1);
      return prevCount + 1;
    );

    console.log("onclick");
  ;

  console.log("rendering");

  return <button onClick=onClick> Increment count </button>;


ReactDOM.render(<App/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

【讨论】:

以上是关于React - useState 钩子第一次和后续设置状态时的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

反应钩子useState()的真正奇怪的行为

使用 React useState() 钩子更新和合并状态对象

如何为 React 钩子(useState 等)做流类型注释?

React:useState 钩子中的 setState 在啥情况下会导致重新渲染?

在对数组进行排序并传递它之后,React 钩子 useState/setState 不起作用

是否可以使用 React 中的 useState() 钩子在组件之间共享状态?