React秒表组件调用栈问题排查:setInterval早于setStartTime触发
问题根因
问题本质是React状态更新的异步性和闭包捕获问题:
- React的
setState(示例中是setStartTime)不会同步修改当前作用域下的状态变量,只是把更新请求提交到React更新队列,当前startTimer函数执行上下文里的startTime始终是函数调用时的旧值 - 你写的
setInterval回调捕获了当前作用域下的旧startTime,所以第一次启动时回调里一直用初始值0计算,直到第二次点击启动,此时作用域里的startTime已经是第一次点击后更新完成的值,才会正常计算 - 额外存在的错误:
clearInterval的传参错误,应该传timerInterval.current而不是timerInterval本身,timerInterval是ref对象,current属性才存储定时器ID
修复要点
- 提前把计算好的起始时间存在局部变量里,避免依赖异步更新的state值
- 修复
clearInterval的传参错误 - 启动前先清掉已存在的定时器,避免多次点击启动生成多个定时器冲突
- 重置的时候先停掉正在运行的定时器,避免重置后还在继续计时
修复后完整代码
import React from "react"; import { useState, useRef } from "react"; import RunButton from "./RunButton"; const RunTimer = () => { const [timerOn, setTimerOn] = useState(false); const [startTime, setStartTime] = useState(0); const [timerTime, setTimerTime] = useState(0); const timerInterval = useRef(null); const startTimer = () => { // 先清掉已有的定时器,避免重复启动 if (timerInterval.current) clearInterval(timerInterval.current); setTimerOn(true); // 先把计算好的起始时间存到局部变量,不依赖异步更新的state const currentStart = Date.now() - timerTime; setStartTime(currentStart); timerInterval.current = setInterval(() => { setTimerTime(Date.now() - currentStart); }, 1000); }; const stopTimer = () => { setTimerOn(false); // 修复clearInterval传参错误,要取ref的current值 clearInterval(timerInterval.current); timerInterval.current = null; }; const resetTimer = () => { // 重置前先停掉定时器 stopTimer(); setStartTime(0); setTimerTime(0); }; let seconds = Math.floor(timerTime / 1000) % 60; let minutes = Math.floor(timerTime / 1000 / 60) % 60; return ( <div className="text-6xl font-medium text-center"> {minutes < 10 ? `0${minutes}` : minutes}: {seconds < 10 ? `0${seconds}` : seconds} <div className="flex gap-10 justify-around"> <RunButton title="start" action={startTimer} /> <RunButton title="stop" color="red" action={stopTimer} /> <RunButton action={resetTimer} color="yellow" title="reset" /> </div> </div> ); }; export default RunTimer;
内容的提问来源于stack exchange,提问作者Capt.Auh3b
相关产品推荐
相关产品推荐

