如何在 ReactJS 中启动和停止计时器显示

Posted

技术标签:

【中文标题】如何在 ReactJS 中启动和停止计时器显示【英文标题】:How to start and stop timer display in ReactJS 【发布时间】:2021-10-20 22:48:44 【问题描述】:

我正在尝试在 ReactJS 中创建番茄钟。我无法让计时器停止倒计时。

PomView.js

const PomView = () => 
    const [timer, setTimer] = useState(1500)    // 25 minutes
    const [start, setStart] = useState(false)
    var firstStart = useRef(true)
    var tick;

    useEffect( () => 
        if (firstStart.current) 
            console.log("first render, don't run useEffect for timer")
            firstStart.current = !firstStart.current
            return
        

        console.log("subsequent renders")
        console.log(start)
        if (start) 
            tick = setInterval(() => 
            setTimer(timer => 
                timer = timer - 1
                console.log(timer)
                return timer       
                
            )
        , 1000)
     else 
        console.log("clear interval")
        clearInterval(tick);
    
    , [start])

    const toggleStart = () => 
        setStart(!start)
    


    const dispSecondsAsMins = (seconds) => 
        // 25:00 
        console.log("seconds " + seconds)
        const mins = Math.floor(seconds / 60)
        const seconds_ = seconds % 60
        return mins.toString() + ":" + ((seconds_ == 0) ? "00" : seconds_.toString())
    

    return (
        <div className="pomView">
            <ul>
                <button className="pomBut">Pomodoro</button>
                <button className="pomBut">Short Break</button>
                <button className="pomBut">Long Break</button>
            </ul>
            <h1>dispSecondsAsMins(timer)</h1>
            <div className="startDiv">
                /* event handler onClick is function not function call */
                <button className="startBut" onClick=toggleStart>!start ? "START" : "STOP"</button>
                start && <AiFillFastForward className="ff" onClick="" />
            </div>
        </div>
    )


export default PomView

虽然clearInterval 在useEffect 的else 部分运行,但计时器仍在计时。我不确定是不是因为 useEffect 中的异步 setTimer 方法。我想知道我写的代码有什么问题。

【问题讨论】:

【参考方案1】:

您将计时器 ref 存储在 tick 中,但每次组件重新渲染时,前一次渲染中的 tick 值都会丢失。您还应该将 tick 存储为 React ref。

你也在改变 timer 状态。

setTimer((timer) => 
  timer = timer - 1; // mutation
  return timer;
);

只返回当前值减1:setTimer((timer) =&gt; timer - 1);

代码

const PomView = () => 
  const [timer, setTimer] = useState(1500); // 25 minutes
  const [start, setStart] = useState(false);
  const firstStart = useRef(true);
  const tick = useRef(); // <-- React ref

  useEffect(() => 
    if (firstStart.current) 
      firstStart.current = !firstStart.current;
      return;
    

    if (start) 
      tick.current = setInterval(() =>  // <-- set tick ref current value
        setTimer((timer) => timer - 1);
      , 1000);
     else 
      clearInterval(tick.current); // <-- access tick ref current value
    

    return () => clearInterval(tick.current); // <-- clear on unmount!
  , [start]);

  ...
;

【讨论】:

感谢您的帮助!我不确定在重新渲染期间保留了什么以及丢失了什么。每当 react 重新渲染函数组件的 return 部分时,无论状态变量如何变化都会运行。只有useStateuseRef 中的变量保留了之前的状态? @calveeen 在您的代码中,是的,但也可以通过useMemouseCallback 钩子记住值,并且useReducer 在组件状态方面类似于useState。我建议阅读官方React docs 关于 React hooks 的内容。基本上所有的局部变量和函数都会在每个渲染周期重新声明。 setTimer((timer) =&gt; timer - 1); 和我写它的方式是否也有区别,因为两个代码都做同样的事情。我不确定你所说的改变计时器状态是什么意思 @calveeen 是的,有区别。虽然它们都完成了相同的操作,说timer = timer - 1 更新了先前的状态值,但它类似于执行timer--。最好声明一个新变量,更新并返回它,即const newTimer = timer - 1; return newTimer;。在 React 中,您希望避免状态突变。这里看起来是良性的,但如果状态更复杂,它确实会造成严重破坏并且难以追踪和调试。 我明白了。谢谢!【参考方案2】:
useEffect( () => 
    const tick= setInterval(fun, 1000);
    return ()=>
      clearInterval(tick);
    
, [])

useEffect 有它自己的发布方式。

【讨论】:

以上是关于如何在 ReactJS 中启动和停止计时器显示的主要内容,如果未能解决你的问题,请参考以下文章

如何停止和启动计时器?

SwiftUI - 如何制作启动/停止计时器

我将如何更改我的程序,以便在方法正确后计时器停止

如何停止在 C# 类中运行的计时器

如何等到计时器在java中停止?

Qt中如何停止定时器timer?