当在单击处理程序中使用两个 useState 资源库时,只有一个有效



我在 react 中使用钩子制作了一个小的秒表组件。这是演示问题的最小代码。

查看名为resetTicks的函数。它有两个setTickssetTicking的设置器,只有setTicking工作,即时钟被暂停,有趣的是,如果我再次单击按钮,它才会重置时钟。我尝试对两个设置器的调用重新排序,但无济于事。

const StopWatch = () => {
const [ticks,setTicks] = useState(0);
const [ticking,setTicking] = useState(false);
useEffect(() => {
setTimeout(() => {
if (ticking) setTicks(ticks + 1);
},10);
},[ticks,ticking]);
const toggleTicking = e => {
setTicking(!ticking);
}
const resetTicks = e => {
// these two setters are causing the issue
// only the setTicking is actually showing effect. I have tried switching 
// their order but nothing works.
setTicking(false); 
setTicks(0);
}
const min = Math.floor(ticks / 6000);
const sec = Math.floor((ticks - (min * 6000)) / 100);
const centis = ticks % 100;
return (
<WatchWrapper>
<WatchDisplay>
<span>{min < 10 ? '0': ''}{min}</span>
<span>:</span>
<span>{sec < 10 ? '0': ''}{sec}</span>
<span>:</span>
<span>{centis < 10 ? '0' : ''}{centis}</span>
</WatchDisplay>
<WatchControls>
<WatchBtn onClick={toggleTicking}>
{ticking ? 'stop' : 'play_arrow'} 
</WatchBtn>
<WatchBtn onClick={resetTicks}>refresh</WatchBtn>
</WatchControls>
</WatchWrapper>
)
}

这是一个棘手的问题,你应该从console.log中了解发生了什么:

true
56
true
57
true
58
true
59
false
0
false
60

它确实设置为 0,但显然在某个时候,预定的旧setTimeout会触发,当它为 60 时,它的旧 tick 值关闭,因此它会将其重置回它。

增加超时,说 3 秒做一个console.log(ticking, ticks)在渲染中,问题应该对您来说更明显。

这是因为setTickssetter 和setTimeout内部异步调用回调之间的竞争条件。setTicks设置器更新即时报价计数,但旧的即时报价计数已存储在setTimeout范围内。因此,setTimeout会引发回调,将其旧值ticks作为参数。您需要清理组件卸载时的setTimeout,以防止出现以下情况:

useEffect(() => {
const timeout = setTimeout(() => {
if (ticking) setTicks(ticks + 1);
}, 10);
return () => clearTimeout(timeout);
}, [ticking, ticks]);

为了确保没有竞争条件,你可以尝试通过为计时器创建一个 React 引用来重置 setTimeout,然后再重置 resetTicks。

const [ticks,setTicks] = React.useState(0);
const [ticking,setTicking] = React.useState(false);
const timer = React.createRef();
React.useEffect(() => {
timer.current = setTimeout(() => {
if (ticking) setTicks(ticks + 1);
},10);
},[ticks,ticking, timer]);
const toggleTicking = e => {
setTicking(!ticking);
}
const resetTicks = e => {
clearTimeout(timer.current);
setTicking(false); 
setTicks(0);
}

在这里使用代码沙盒进行测试:

https://codesandbox.io/embed/test-reset-race-condition-g53l5?fontsize=14&hidenavigation=1&theme=dark&view=editor

useEffect(() => {
const interval = setInterval(() => {
if (ticking) setTicks(prevState => prevState + 1);
}, 10);
return () => clearInterval(interval);
}, [ticking]);

相关内容

  • 没有找到相关文章

最新更新