【问题标题】:What is the problem here with setTimeout and Closure?setTimeout 和 Closure 有什么问题?
【发布时间】:2021-10-21 10:21:00
【问题描述】:

首先,我无法找出这段代码中的问题。但这个问题可能在这里占有一席之地。

据我了解,问题可能是单击按钮后计数器值未更新。单击按钮时,警报会显示该值,尽管在 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

【问题讨论】:

  • “据我了解,问题可能是点击按钮后计数器值没有更新。” 是和否。 counter 已更新,但 setTimeout 已被调用,因此引用“旧”counter 变量。使用 refs 是解决此问题的一种简单方法。
  • 这可能有助于更好地理解闭包:JavaScript closure inside loops – simple practical example。使用 React 组件的情况与使用循环的情况非常相似。每次重新渲染基本上都是一个新的迭代。

标签: javascript reactjs asynchronous settimeout


【解决方案1】:

问题

setTimeout 被调用时,它的回调函数关闭Root 组件的当前渲染中count 的当前值。

在计时器到期之前,如果您更新count,这会导致组件重新渲染,但setTimeout 的回调函数仍然会看到在效果 setTimeout 被调用。这是代码中的闭包引起的问题的要点。

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

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

解决方案

您可以使用useRef钩子来摆脱因关闭而导致的问题。

您可以在每次重新渲染Root 组件时更新引用。这使我们可以在 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>

【讨论】:

  • 这里不用useEffect,直接countRef.current = count;即可。
  • 可能值得注意的是,每个渲染都会创建一个新的handleAlertClick 函数,该函数捕获counter 的当前值。来自旧渲染通道的handleAlertClick 函数完全独立于来自新渲染通道的handleAlertClick 函数。
  • @FelixKling 对。已更新。
猜你喜欢
  • 2014-03-22
  • 1970-01-01
  • 1970-01-01
  • 2013-12-23
  • 2011-12-25
  • 1970-01-01
  • 2021-08-25
  • 2014-11-28
  • 1970-01-01
相关资源
最近更新 更多