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

组件路由时倒计时计时器停止渲染且重置问题求助

问题解决:路由切换后倒计时计时器重置且定时器泄漏的问题

问题根源

  1. 组件卸载时未清除定时器:路由切换到Timer组件时,Countdown组件被卸载,但setInterval创建的定时器仍在后台运行,造成内存泄漏。
  2. 闭包捕获旧值:updateTime函数里的time是组件初始渲染时捕获的闭包值,后续setTime更新后,这个值不会同步,导致计算的剩余时间错误。
  3. 状态未持久化:组件卸载后state丢失,重新挂载时回到初始值600。

解决方案

方案1:修复定时器泄漏 + 闭包问题 + 本地存储持久化

修改Countdown组件,通过useRef保存定时器ID,组件卸载时清除;用函数式更新获取最新state;同时用localStorage持久化剩余时间:

import { useState, useRef, useEffect } from "react";
import { Link } from "react-router-dom";

const Countdown = () => {
  // 从localStorage读取初始值,没有则用600
  const [time, setTime] = useState(() => {
    const saved = localStorage.getItem("countdownTime");
    return saved ? parseInt(saved, 10) : 600;
  });
  const intervalRef = useRef(null);
  const startTimeRef = useRef(null);

  // 组件卸载时清除定时器
  useEffect(() => {
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    };
  }, []);

  // 每次time变化时保存到localStorage
  useEffect(() => {
    localStorage.setItem("countdownTime", time.toString());
  }, [time]);

  const startTimer = () => {
    if (intervalRef.current) return; // 防止重复启动
    startTimeRef.current = Date.now();
    intervalRef.current = setInterval(() => {
      // 用函数式更新获取最新的time值
      setTime(prevTime => {
        const elapsed = ((Date.now() - startTimeRef.current) / 1000) | 0;
        const updatedTime = prevTime - elapsed;
        // 重置定时器并停止计时
        if (updatedTime <= 0) {
          clearInterval(intervalRef.current);
          intervalRef.current = null;
          return 0;
        }
        startTimeRef.current = Date.now(); // 重置起始时间,避免累计误差
        return updatedTime;
      });
      console.log("Set Interval running");
    }, 1000);
  };

  const minutes = (time / 60) | 0;
  const seconds = time % 60 | 0;
  return (
    <>
      <h3>
        {minutes.toString().padStart(2, "0")}:{seconds.toString().padStart(2, "0")}
      </h3>
      <p>先启动计时器 👇</p>
      <button onClick={startTimer} disabled={intervalRef.current !== null}>
        {intervalRef.current ? "运行中" : "START"}
      </button>
      <p>启动后跳转到其他组件 👇</p>
      <Link to="timer">
        <button>前往Timer组件</button>
      </Link>
    </>
  );
};

export default Countdown;

方案2:状态提升到App组件(更适合跨组件共享状态)

如果需要在多个组件中共享计时器状态,可以把time和定时器控制逻辑放到App组件,通过props传递给Countdown:

// App组件
import { Route, Routes, useState, useRef, useEffect } from "react-router-dom";
import Countdown from "./components/Countdown";
import Timer from "./components/Timer";

const App = () => {
  const [time, setTime] = useState(600);
  const intervalRef = useRef(null);
  const startTimeRef = useRef(null);

  useEffect(() => {
    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current);
    };
  }, []);

  const startTimer = () => {
    if (intervalRef.current) return;
    startTimeRef.current = Date.now();
    intervalRef.current = setInterval(() => {
      setTime(prev => {
        const elapsed = ((Date.now() - startTimeRef.current)/1000)|0;
        const updated = prev - elapsed;
        if (updated <=0) {
          clearInterval(intervalRef.current);
          intervalRef.current = null;
          return 0;
        }
        startTimeRef.current = Date.now();
        return updated;
      });
    },1000);
  };

  return (
    <>
      <Routes>
        <Route path="/" element={<Countdown time={time} startTimer={startTimer} isRunning={!!intervalRef.current} />} />
        <Route path="timer" element={<Timer />} />
      </Routes>
    </>
  );
};

export default App;

修改Countdown组件,接收props:

import { Link } from "react-router-dom";

const Countdown = ({ time, startTimer, isRunning }) => {
  const minutes = (time / 60) | 0;
  const seconds = time % 60 | 0;
  return (
    <>
      <h3>
        {minutes.toString().padStart(2, "0")}:{seconds.toString().padStart(2, "0")}
      </h3>
      <p>先启动计时器 👇</p>
      <button onClick={startTimer} disabled={isRunning}>
        {isRunning ? "运行中" : "START"}
      </button>
      <p>启动后跳转到其他组件 👇</p>
      <Link to="timer">
        <button>前往Timer组件</button>
      </Link>
    </>
  );
};

export default Countdown;

关键改进点

  • 清除定时器:利用useEffect的清理函数,在组件卸载时调用clearInterval,避免内存泄漏。
  • 函数式更新state:setTime(prevTime => ...)确保每次获取的都是最新的状态值,解决闭包问题。
  • 状态持久化:通过localStorage或状态提升,保证组件重新挂载时不会丢失之前的计时状态。
  • 防止重复启动:通过intervalRef判断定时器是否已在运行,避免多次点击创建多个定时器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:30:41