setTimeout 和 Closure 的问题是什么?

What is the problem here with setTimeout and Closure?

首先,我无法找出这段代码中的问题。但可能这里有问题。

据我了解,问题可能是点击按钮后计数器值没有更新。警报显示单击按钮时的值,尽管在 2.5 秒的延迟期间我单击并增加了计数器的值。

我说得对吗?如果是的话,这里应该修复或添加什么?

import React, { useState } from 'react'

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

  function handleAlertClick() {
    setTimeout(() => {
       alert(`You clicked ${count} times`)
      }, 2500)
  }

  return (
    <Container>
      <Column>
        <h4>Closures</h4>
        <p>You clicked {count} times</p>
        <button type="button" onClick={() => setCount(counter => counter + 1)}>
          Click me
        </button>
        <button type="button" onClick={handleAlertClick}>
          Show alert
        </button>
      </Column>
    </Container>
  )
}

export default Root

问题

setTimeout 被调用时,它的回调函数 关闭 在 [=14= 的当前渲染中 count 的当前值] 组件。

在计时器到期之前,如果您更新 count,将导致组件重新渲染,但 setTimeout 的回调函数仍然看到 [=49= 中的值]effect 当 setTimeout 被调用时。这是您代码中的闭包导致的问题的要点。

Root 组件的每个渲染器都有自己的状态、道具、组件内部定义的局部函数;简而言之,组件的每个渲染都与之前的组件分开。

状态在组件的特定渲染中是不变的;组件在重新渲染之前看不到更新的状态。在之前的渲染中设置的任何计时器都会看到它 关闭 的值;它看不到更新后的状态。

解决方案

您可以使用 useRef 钩子来解决由于关闭引起的问题。

您可以在每次重新渲染 Root 组件时更新 ref。这允许我们在 ref.

中保存 count 的最新值

一旦你有一个 ref,而不是将 count 传递给 alert,而是传递 ref。这确保警报始终显示 count.

的最新值

function Root() {
  const [count, setCount] = React.useState(0)
  const countRef = React.useRef(count);

  // assign the latest value of "count" to "countRef"
  countRef.current = count;
  
  function handleAlertClick() {
    setTimeout(() => {
       alert(`You clicked ${countRef.current} times`)
      }, 2500)
  }

  return (
      <div>
        <h4>Closures</h4>
        <p>You clicked {count} times</p>
        <button type="button" onClick={() => setCount(counter => counter + 1)}>
          Click me
        </button>
        <button type="button" onClick={handleAlertClick}>
          Show alert
        </button>
      </div>
  )
}

ReactDOM.render(<Root/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

<div id="root"></div>