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

gau*_*430 15 javascript reactjs react-hooks

考虑这个代码:

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'))
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

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

1
onclick 
2
3
rendering 
Run Code Online (Sandbox Code Playgroud)

我原以为输出是

onclick 
1
2
3
rendering
Run Code Online (Sandbox Code Playgroud)

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

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

onclick 
4
5
6
rendering
Run Code Online (Sandbox Code Playgroud)

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

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>;
}
Run Code Online (Sandbox Code Playgroud)

预期的输出是:

onclick 
2
3
4
rendering 
Run Code Online (Sandbox Code Playgroud)

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

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

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

Jos*_*Lin 1

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

useEffect当状态/属性改变时设置回调;以下代码片段显示了如何在count每次更改时进行记录。您可以参考文档以获取更多信息。

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'))
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)