Javascript 在单击处理程序中使用两个useState设置程序时,只有一个有效

Javascript 在单击处理程序中使用两个useState设置程序时,只有一个有效,javascript,reactjs,react-hooks,jsx,use-state,Javascript,Reactjs,React Hooks,Jsx,Use State,我用react中的挂钩制作了一个小型秒表组件。这是演示问题的最少代码 查看名为resetTicks的函数。它有两个设置器setTicks和setTicking,只有setTicking起作用,即时钟暂停,有趣的是,如果我再次单击按钮,它才会重置时钟。我试着对两个setter重新排序调用,但都没有用 const StopWatch = () => { const [ticks,setTicks] = useState(0); const [ticking,setTicking] =

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

查看名为
resetTicks
的函数。它有两个设置器
setTicks
setTicking
,只有
setTicking
起作用,即时钟暂停,有趣的是,如果我再次单击按钮,它才会重置时钟。我试着对两个setter重新排序调用,但都没有用

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>
    )
}
const秒表=()=>{
const[ticks,setTicks]=useState(0);
常数[ticking,setTicking]=使用状态(false);
useffect(()=>{
设置超时(()=>{
如果(滴答声)设置滴答声(滴答声+1);
},10);
},[滴答声,滴答声];
常量toggleTicking=e=>{
设置滴答声(!滴答声);
}
const resetTicks=e=>{
//这两个二传手造成了问题
//只有设置滴答声才真正起作用。我试过切换
//他们的命令无效。
设置滴答声(假);
滴答声(0);
}
最小常数=数学楼层(刻度/6000);
常数秒=数学楼层((刻度-(最小*6000))/100);
常数厘米=百分之一百;
返回(
{min<10?'0':''}{min}
:
{sec<10?'0':''}{sec}
:
{centis<10?'0':''}{centis}
{滴答声?'stop':'play_arrow'}
刷新
)
}

这是一个棘手的问题,您应该从
控制台了解发生了什么。log

true
56
true
57
true
58
true
59
false
0
false
60
它确实被设置为0,但显然在某个点上,计划触发的旧
setTimeout
,在其为60时,在旧的tick值上有一个闭包,因此它将其重置回原来的值

将超时时间增加到3秒,执行
console.log(滴答声,滴答声)

在渲染中,您应该更清楚问题所在。

这是一个棘手的问题,您应该从
控制台了解发生了什么。log

true
56
true
57
true
58
true
59
false
0
false
60
  useEffect(() => {
    const interval = setInterval(() => {
      if (ticking) setTicks(prevState => prevState + 1);
    }, 10);

    return () => clearInterval(interval);
  }, [ticking]);
它确实被设置为0,但显然在某个点上,计划触发的旧
setTimeout
,在其为60时,在旧的tick值上有一个闭包,因此它将其重置回原来的值

将超时时间增加到3秒,执行
console.log(滴答声,滴答声)

在render中,您应该更清楚问题所在。

这是因为
setTicks
setter和
setTimeout
内部回调的异步调用之间存在竞争条件。
setTicks
setter更新ticks计数,但旧的ticks计数已存储在
setTimeout
范围内。因此,
setTimeout
会引发回调,并将
ticks
的旧值作为参数提供给它。您需要在卸载组件时清除
setTimeout
,以防止:

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

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

这是因为
setTicks
setter和
setTimeout
内部回调的异步调用之间存在竞争条件。
setTicks
setter更新ticks计数,但旧的ticks计数已存储在
setTimeout
范围内。因此,
setTimeout
会引发回调,并将
ticks
的旧值作为参数提供给它。您需要在卸载组件时清除
setTimeout
,以防止:

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

为了确保不存在竞争条件,您可以通过为计时器创建React引用,在resetTicks之前尝试重置setTimeout

const[ticks,setTicks]=React.useState(0);
常数[ticking,setTicking]=React.useState(false);
常量计时器=React.createRef();
React.useffect(()=>{
timer.current=setTimeout(()=>{
如果(滴答声)设置滴答声(滴答声+1);
},10);
},[滴答声,滴答声,计时器];
常量toggleTicking=e=>{
设置滴答声(!滴答声);
}
const resetTicks=e=>{
clearTimeout(定时器电流);
设置滴答声(假);
滴答声(0);
}
在此处使用Codesandbox进行测试:


为了确保不存在竞争条件,您可以通过创建计时器的React引用,在resetTicks之前尝试重置setTimeout

const[ticks,setTicks]=React.useState(0);
常数[ticking,setTicking]=React.useState(false);
常量计时器=React.createRef();
React.useffect(()=>{
timer.current=setTimeout(()=>{
如果(滴答声)设置滴答声(滴答声+1);
},10);
},[滴答声,滴答声,计时器];
常量toggleTicking=e=>{
设置滴答声(!滴答声);
}
const resetTicks=e=>{
clearTimeout(定时器电流);
设置滴答声(假);
滴答声(0);
}
在此处使用Codesandbox进行测试:


最初,您将记号的值定义为0,并在单击定义后将其定义为
setTicks(0)
,因此不会重新渲染组件。尝试将其设置为0以外的其他值@DurgeshPal否。时钟运行时,我正在单击重置按钮,因此由于许多不同值的
setTicks
调用,节拍已经更改。是。但我认为它在这两者之间设置了0值。最初,您将ticks的值定义为0,然后单击定义它
setTicks(0)
,因此不会重新渲染组件。尝试将其设置为0以外的其他值@DurgeshPal否。时钟运行时,我正在单击重置按钮,因此由于许多不同值的
setTicks
调用,节拍已经更改。是。但我认为,不知何故,它在两者之间设置了0值。你的眼光真敏锐。为什么我连这个都没想到。有趣的是,我在写“伙计,有一个场景,这个超时触发了预先安排好的时间,我会遇到问题”的时候,脑子里就有这个想法,但当实际问题发生时,我没有想到。哦,天哪。你的眼光真敏锐。为什么我连这个都没想到。有趣的是,当我写下“男人在那里”这句话的时候,我脑子里就有了它