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

React函数组件实现番茄钟定时器时setState未生效问题求助

问题原因
  • setWorkRest 是异步更新的 React 状态 API,调用后不会立刻修改当前作用域下的 workRest 变量,你紧接着读取的 workRest 还是更新前的旧值,所以状态切换逻辑不生效
  • 你把 minutes、seconds 定义为组件内的普通 let 变量,既不是 React 状态也不是 ref:组件每次重渲染都会把这两个变量重置为0,同时 setInterval 形成的闭包会一直捕获首次渲染时的旧变量、旧状态,导致后续状态更新无法被定时器逻辑识别
  • 没有存储 setInterval 返回的定时器 ID,既无法在状态切换时重置定时器,也会导致重复触发启动逻辑后多个定时器同时运行,引发逻辑混乱
  • 直接操作原生 DOM 读取输入框值不符合 React 设计范式,也容易出现取值异常
修正后完整代码
import React, { useState, useRef, useEffect } from "react";

function Challenge20() {
  const [timer, setTimer] = useState('');
  const [workMin, setWorkMin] = useState(25);
  const [restMin, setRestMin] = useState(5);
  const [workRest, setWorkRest] = useState('work');
  const timerRef = useRef(null);
  // 用ref存秒数和分钟数,避免闭包陷阱和重渲染重置
  const secondsRef = useRef(0);
  const minutesRef = useRef(0);

  function reduceSeconds() {
    let curSec = secondsRef.current;
    let curMin = minutesRef.current;
    
    // 格式化显示
    const formatSec = curSec < 10 ? `0${curSec}` : curSec;
    setTimer(`${curMin}:${formatSec}`);

    secondsRef.current -= 1;

    if (secondsRef.current < 1 && curMin > 0) {
      secondsRef.current = 59;
      minutesRef.current -= 1;
    } else if (secondsRef.current === 0 && curMin === 0) {
      // 切换状态,用函数式更新拿到最新的状态值
      setWorkRest(prev => {
        const newStatus = prev === 'work' ? 'rest' : 'work';
        // 直接用新状态赋值时长
        minutesRef.current = newStatus === 'work' ? workMin -1 : restMin -1;
        secondsRef.current = 59;
        return newStatus;
      });
    }
  };

  function startTimer() {
    // 避免重复启动定时器
    if (timerRef.current) clearInterval(timerRef.current);
    document.getElementById('start').style.display = 'none';
    minutesRef.current = workMin - 1;
    secondsRef.current = 59;
    timerRef.current = setInterval(reduceSeconds, 1000);
  };

  // 组件卸载清除定时器,避免内存泄漏
  useEffect(() => {
    return () => {
      if (timerRef.current) clearInterval(timerRef.current);
    }
  }, [])

  return (
    <>  
      <label>Work Minutes:</label>
      <input 
        id='work' 
        type='number' 
        max='60'
        value={workMin}
        onChange={(e) => setWorkMin(Number(e.target.value))}
      />
      <br/>
      <label>Rest Minutes:</label>
      <input 
        id='rest' 
        type='number' 
        max='60'
        value={restMin}
        onChange={(e) => setRestMin(Number(e.target.value))}
      />
      <br/>
      <br/>
      <span id='timer'>{workRest} -> {timer}</span>
      <button id='start' onClick={startTimer}>Start!</button>
    </>
  );
};

export default Challenge20;
核心修改点
  • 用 useRef 存储定时器ID、分钟、秒数,解决闭包陷阱和重渲染变量重置问题
  • setWorkRest 改用函数式更新,在回调里可以拿到最新的状态值,避免异步更新导致的取值旧问题
  • 把工作时长、休息时长改为 React 状态托管,不再操作原生DOM读取值
  • 新增定时器防重复逻辑和组件卸载清除逻辑,避免多个定时器同时运行

内容的提问来源于stack exchange,提问作者Sasquatch

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 11:06:04