React Hooks中如何创建带重置功能的秒计数计时器?
React计时器实现方案及常见问题解答
一、你的代码为什么会崩溃?
你写的while (second < 60)是同步循环,会瞬间创建几万甚至几十万个setTimeout——循环根本不会等定时器执行,而且React的setState是异步更新的,循环里的second值自始至终都是初始的旧值,导致循环条件永远成立,直接把页面卡死。
另外,定时器里的setSecond(second + 1)也有问题:因为闭包,回调函数捕获的是初始的second值,每次执行都会把秒数设成初始值+1,根本不会持续递增。
二、计时器的最佳实现方式
推荐用setInterval配合useRef来做,既能保证计时正常,还能避免内存泄漏。完整代码如下:
import { useState, useRef, useEffect } from 'react'; const Timer = () => { const [hour, setHour] = useState(4); const [minute, setMinute] = useState(8); const [second, setSecond] = useState(12); // 用useRef存定时器ID,跨渲染周期也能拿到 const timerRef = useRef<NodeJS.Timeout | null>(null); const setTime = (value: string) => { if (value === 'reset') { // 重置前先清定时器 if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } setHour(0); setMinute(0); setSecond(0); } }; const startTime = () => { // 防止重复点START创建多个定时器 if (timerRef.current) return; timerRef.current = setInterval(() => { // 用函数式更新拿最新的state,避开闭坑 setSecond(prevSecond => { // 处理秒到60的进位 if (prevSecond + 1 >= 60) { setMinute(prevMinute => { // 处理分钟到60的进位 if (prevMinute + 1 >= 60) { setHour(prevHour => prevHour + 1); return 0; } return prevMinute + 1; }); return 0; } return prevSecond + 1; }); }, 1000); }; // 组件卸载时清定时器,避免内存泄漏 useEffect(() => { return () => { if (timerRef.current) clearInterval(timerRef.current); }; }, []); return ( <div className="d-flex justify-content-center align-items-center"> <div className="me-3 fs-4"> {hour}:{minute.toString().padStart(2, '0')}:{second.toString().padStart(2, '0')} </div> <button className="btn btn-outline-danger me-2" onClick={() => setTime('reset')} > RESET </button> <button className="btn btn-outline-success" onClick={startTime} > START </button> </div> ); }; export default Timer;
核心注意点:
useRef用来存定时器ID,保证组件重渲染后还能找到这个定时器,方便后续清除。- 用
setSecond(prevSecond => ...)这种函数式更新,每次都能拿到最新的秒数,不会因为闭包拿旧值。 - 点START前先检查有没有正在运行的定时器,避免重复创建。
- 重置和组件卸载时必须清除定时器,不然组件没了定时器还在跑,会造成内存泄漏。
- 加上了时分秒的进位逻辑,符合真实计时器的逻辑。
三、Hooks里能使用循环吗?
可以用,但得分场景:
- 正常场景:比如遍历数组渲染元素(
list.map(item => <div key={item.id}>{item}</div>))、处理数据时的同步循环(比如算数组总和),这些都没问题。 - 错误场景:像你之前那样用同步循环触发大量异步更新,直接导致页面崩溃,绝对不能这么干。如果要做定时的循环操作,要么用
setInterval,要么用递归的setTimeout(比如每次定时器执行完再调用下一个setTimeout)。
内容的提问来源于stack exchange,提问作者João Denari
相关产品推荐
相关产品推荐

