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
相关产品推荐
相关产品推荐

