如何在 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) => 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
部分时,无论状态变量如何变化都会运行。只有useState
和useRef
中的变量保留了之前的状态?
@calveeen 在您的代码中,是的,但也可以通过useMemo
和useCallback
钩子记住值,并且useReducer
在组件状态方面类似于useState
。我建议阅读官方React docs 关于 React hooks 的内容。基本上所有的局部变量和函数都会在每个渲染周期重新声明。
setTimer((timer) => 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 中启动和停止计时器显示的主要内容,如果未能解决你的问题,请参考以下文章