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>
首先,我无法找出这段代码中的问题。但可能这里有问题。
据我了解,问题可能是点击按钮后计数器值没有更新。警报显示单击按钮时的值,尽管在 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>