1

单击按钮后,控制台显示 0 和页面 1

function App() {
  const [count, setCount] = useState(0);
  
  const addOne = () => {
    setCount(count + 1)

    console.log(count)
  }
  
  return (
    <>
      <p>{count}</p>
      <button onClick={addOne}>Add</button>
    </>
  );
}

我认为是因为它setCount()是异步发生的,但即使我添加一个 setTimeout 到console.log(),控制台仍然显示未更新状态

为什么???

4

3 回答 3

4

React 中的状态更新始终是异步的。你会在 useEffect 中找到更新后的 count 状态值

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

  
  useEffect(()=> {
      console.log('count',count);
  },[count])
  
  const addOne = () => {
    setCount(count + 1)
  } 

  return (
    <>
      <p>{count}</p>
      <button onClick={addOne}>Add</button>
    </>
  );
}
于 2021-02-04T04:17:07.620 回答
3

闭包

由于关闭,您在控制台日志中遇到未更新状态。

在渲染组件时创建函数,并在创建闭包时使用 count 的值创建闭包。

如果 count 的值为 0,并且您的组件重新呈现,则将创建函数的闭包并将其附加到 onlcick 的事件侦听器。

在这种情况下,您的组件的第一次渲染

 const addOne = () => {
    setCount(count + 1)

    console.log(count)
  }

相当于(将计数替换为 0)

 const addOne = () => {
    setCount(0 + 1)

    console.log(0)
  }

因此,在您的情况下,控制台记录时计数为 0 是有意义的。

在这种情况下,我相信您遇到的关闭与 setState 的异步行为相结合

异步行为

密码箱

当异步操作发生时,异步行为会成为一个问题。setTimeout 是基本的异步操作之一。异步操作始终要求您向 setCount 函数提供一个函数,该函数将接受最新状态作为参数,nextState 是此函数的返回值。这将始终确保使用当前状态来计算下一个状态,无论何时异步执行。

  const addOneAsync = () => {
    setCountAsync((currentState) => {
      const nextState = currentState + 1;
      console.log(`nextState async ${nextState}`);
      return nextState;
    });
  };

我创建了一个代码框来展示这一点的重要性。快速单击“计数”按钮 4 次。(或任意次数)并观察计数结果如何不正确,countAsync 结果在哪里正确。

addOneAsync:当按钮被点击时,会在 周围创建一个闭包addOneAsync,但是由于我们使用的是一个接受 currentState 的函数,所以当它最终触发时,当前状态将用于计算下一个状态

addOneaddOne:当单击按钮时,会在 count 被捕获为单击时的值的位置周围创建一个闭包。如果在 count 增加之前单击 count 按钮 4 次,您将触发 4 个 addOne 闭包,其中 count 被捕获为 0。

所有 4 次超时都将触发并简单地将计数设置为 0 + 1,因此计数的结果为 1。

于 2021-02-04T04:52:34.660 回答
1

是的,您对这种行为的起源是正确的,这里的其他海报似乎已经解释了如何解决它。但是,我看不到您的具体问题的答案:

...但是即使我在 console.log() 中添加了一个 setTimeout,控制台仍然显示未更新状态为什么???

所以你的意思是,即使你像这样处理 console.log 调用:

  const addOne = () => {
    setCount((count) => count + 1);
    setTimeout(() => console.log(count), 1000);
  }

它仍然会打印旧的、未更新的值count。为什么?超时不应该允许count更新时间吗?我将引用答案:

这是微妙但预期的行为。安排 setTimeout 时,它使用预定count时间的值。它依靠闭包来异步访问计数。当组件重新渲染时,会创建一个新的闭包,但这不会改变最初关闭的值。

来源:https ://github.com/facebook/react/issues/14010#issuecomment-433788147

所以你有它。

于 2021-02-04T04:55:25.843 回答