如何在处理函数的 setInterval 中使用 React 状态变量
How to use React state variables in setInterval in a handler function
我的代码看起来像下面的代码(我删掉了大部分代码以突出问题)。我试图在单击按钮时调用一个调用 setInterval 的函数来增加父组件的状态。不过,记录的只是“0”。 “setPercent”调度函数似乎工作正常 - 在调用它之后我知道“百分比”正在更新为 1,但是当它在下一个间隔返回时,控制台再次记录 0 并且设置再次设置为 1。我假设正在发生的事情是,当创建“clickHandler”函数时,它很难复制“percent”的值而不是引用它 - 我不知道为什么以及如何修复它。非常感谢任何帮助!
const Component = () => {
const [percent, setPercent] = useState(0)
const clickHandler = (time) => {
let timer = setInterval(() => {
if (percent < 100) {
console.log('current percent', percent)
setPercent(percent + 1)
} else {
clearInterval(timer)
}
}, time / 100)
}
return (
<div>
<Button onClick={clickHandler} />
</div>
)
}
setState
的函数形式在这里应该有用。当给定一个函数时,setState
以当前值作为参数调用该函数(而不是通过闭包访问的旧值)。以下应该有效:
const Component = () => {
const [percent, setPercent] = useState(0);
const clickHandler = (time) => {
let timer = setInterval(() => {
setPercent((current) => {
if (current < 100) {
console.log('current percent', current)
return current + 1;
} else {
clearInterval(timer);
return current;
}
});
}, time / 100);
}
return (
<div>
<Button onClick={clickHandler} />
</div>
)
}
虽然在 setState
函数中有像 clearInterval
这样的副作用感觉并不理想。这是使用 useEffect
停止计时器的另一种方法,它也强制使用单个计时器(即使按钮被多次单击):
const Component = () => {
const [percent, setPercent] = useState(0);
const [timer, setTimer] = useState();
// Stop running timer when we reach 100%
useEffect(() => {
if (percent >= 100 && timer != null) {
clearInterval(timer);
setTimer(null);
}
}, [percent, timer]);
const clickHandler = (time) => {
// Stop any existing timer
if (timer) {
clearTimer(timer);
}
// Start new timer
setTimer(setInterval(() => {
setPercent((current) => current + 1);
}, time / 100));
}
return (
<div>
<Button onClick={clickHandler} />
</div>
)
}
这里发生的一些事情可能会误导您。
首先,每次更改百分比时,您的组件都会重新呈现。这可能导致有多个间隔 运行。
其次,在每次重新渲染时都会创建一个百分比 const 的新实例,这意味着每个间隔都使用创建间隔时它所持有的值进行操作,这会导致冲突的更新值被发送到 useState。
考虑到这一切,IMO 最好的方法是使用 useEffect
和 setTimeout
实现这种性质的 timer/counter
这是一个用于演示解决方案的快速代码沙箱:
https://codesandbox.io/s/cold-voice-cu49u?file=/src/App.js
不要为此感到气馁,我已经掉进了这个陷阱很多我知道陷阱。
我的代码看起来像下面的代码(我删掉了大部分代码以突出问题)。我试图在单击按钮时调用一个调用 setInterval 的函数来增加父组件的状态。不过,记录的只是“0”。 “setPercent”调度函数似乎工作正常 - 在调用它之后我知道“百分比”正在更新为 1,但是当它在下一个间隔返回时,控制台再次记录 0 并且设置再次设置为 1。我假设正在发生的事情是,当创建“clickHandler”函数时,它很难复制“percent”的值而不是引用它 - 我不知道为什么以及如何修复它。非常感谢任何帮助!
const Component = () => {
const [percent, setPercent] = useState(0)
const clickHandler = (time) => {
let timer = setInterval(() => {
if (percent < 100) {
console.log('current percent', percent)
setPercent(percent + 1)
} else {
clearInterval(timer)
}
}, time / 100)
}
return (
<div>
<Button onClick={clickHandler} />
</div>
)
}
setState
的函数形式在这里应该有用。当给定一个函数时,setState
以当前值作为参数调用该函数(而不是通过闭包访问的旧值)。以下应该有效:
const Component = () => {
const [percent, setPercent] = useState(0);
const clickHandler = (time) => {
let timer = setInterval(() => {
setPercent((current) => {
if (current < 100) {
console.log('current percent', current)
return current + 1;
} else {
clearInterval(timer);
return current;
}
});
}, time / 100);
}
return (
<div>
<Button onClick={clickHandler} />
</div>
)
}
虽然在 setState
函数中有像 clearInterval
这样的副作用感觉并不理想。这是使用 useEffect
停止计时器的另一种方法,它也强制使用单个计时器(即使按钮被多次单击):
const Component = () => {
const [percent, setPercent] = useState(0);
const [timer, setTimer] = useState();
// Stop running timer when we reach 100%
useEffect(() => {
if (percent >= 100 && timer != null) {
clearInterval(timer);
setTimer(null);
}
}, [percent, timer]);
const clickHandler = (time) => {
// Stop any existing timer
if (timer) {
clearTimer(timer);
}
// Start new timer
setTimer(setInterval(() => {
setPercent((current) => current + 1);
}, time / 100));
}
return (
<div>
<Button onClick={clickHandler} />
</div>
)
}
这里发生的一些事情可能会误导您。
首先,每次更改百分比时,您的组件都会重新呈现。这可能导致有多个间隔 运行。 其次,在每次重新渲染时都会创建一个百分比 const 的新实例,这意味着每个间隔都使用创建间隔时它所持有的值进行操作,这会导致冲突的更新值被发送到 useState。
考虑到这一切,IMO 最好的方法是使用 useEffect
和 setTimeout
这是一个用于演示解决方案的快速代码沙箱: https://codesandbox.io/s/cold-voice-cu49u?file=/src/App.js
不要为此感到气馁,我已经掉进了这个陷阱很多我知道陷阱。