You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 21:40:58