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

React中useRef存储的setInterval在useCallback内调用clearInterval无法终止

React秒表组件定时器无法终止问题解决方案

问题根因

  • 开启新定时器前未清理旧实例:每次触发handleTimerRun时直接覆盖increment.current的值,之前的定时器ID丢失无法被清除,导致多个定时器并行运行
  • 不必要的依赖项触发重复执行:handleTimerRun的依赖数组包含timerStartTime,每次timerStartTime更新都会生成新的handleTimerRun引用,触发useEffect重新执行,再次创建新的定时器
  • 缺少组件卸载时的清理逻辑:组件卸载时没有主动清除定时器,会造成内存泄漏和残留逻辑执行
  • 定时器闭包捕获旧值:定时器逻辑捕获了首次创建时的timerStartTime闭包值,无法拿到最新的状态判断逻辑

修复方案

  1. 开启定时器前先清理已有实例,避免多定时器并存
  2. 新增ref同步timerStartTime的最新值,解决闭包捕获旧值问题,同时移除handleTimerRun依赖数组中不必要的timerStartTime,避免函数频繁重建
  3. 给useEffect添加清理函数,在依赖变更、组件卸载时主动清理定时器
  4. 清除定时器后将increment.current置为null,避免重复清理无效ID

关键代码修改

import React, { useEffect, useRef, useCallback, useContext } from 'react'
import Container from '@material-ui/core/Container'
import Typography from '@material-ui/core/Typography'

import useLocalStorage from '../../hooks/useLocalStorage'
import { FlowTimeContext } from '../../services/flow/flow-time.context'
import { useStyles } from './grid-stopwatch.styles'

export default function GridStopwatch() {
  const classes = useStyles()

  const { currentActivityOn } = useContext(FlowTimeContext)

  const [timerStartTime, setTimerStartTime] = useLocalStorage('timerStartTime', '')
  // 新增ref同步最新的timerStartTime,解决闭包问题
  const timerStartTimeRef = useRef(timerStartTime)
  const [timer, setTimer] = useLocalStorage('timer', 0)
  const increment = useRef(null)

  // 同步state到ref
  useEffect(() => {
    timerStartTimeRef.current = timerStartTime
  }, [timerStartTime])

  const handleTimerRun = useCallback(() => {
    // 开启新定时器前先清理旧实例
    if (increment.current) {
      clearInterval(increment.current)
    }
    increment.current = setInterval(() => {
      // 从ref取最新值,不需要捕获闭包中的timerStartTime
      if(timerStartTimeRef.current !== ''){
        const new_timer = ((Math.floor(new Date().getTime() / 1000)) - timerStartTimeRef.current)
        setTimer(new_timer)
      } else {
        console.log("Triggered")
      }
    }, 1000)
  // 移除timerStartTime依赖,避免函数频繁重建
  }, [setTimer])

  const handleReset = useCallback(() => {
    if (increment.current) {
      clearInterval(increment.current)
      // 清完后置空,避免重复清理无效ID
      increment.current = null
    }
    setTimer(0)
    setTimerStartTime('')
  }, [setTimer, setTimerStartTime])

  const handleStart = useCallback(() => {
    if(timerStartTime === ''){
      var start = Math.floor(new Date().getTime() / 1000)
      setTimerStartTime(start)
    }
  }, [timerStartTime, setTimerStartTime])

  const formatTime = () => {
    const getSeconds = `0${timer % 60}`.slice(-2)
    const minutes = `${Math.floor(timer / 60)}`
    const getMinutes = `0${minutes % 60}`.slice(-2)
    const getHours = `0${Math.floor(timer / 3600)}`.slice(-2)

    return `${getHours}h : ${getMinutes}m : ${getSeconds}s`
  }

  useEffect(() => {
    if(currentActivityOn === true){
      handleStart()
      handleTimerRun()
    } else {
      handleReset()
    }
    // 新增清理函数,依赖变更、组件卸载时自动清定时器
    return () => {
      if (increment.current) {
        clearInterval(increment.current)
      }
    }
  }, [currentActivityOn, handleStart, handleReset, handleTimerRun])

  return (
    <Container maxWidth='xl' className={classes.container}>
      {currentActivityOn && (
        <Typography variant='subtitle2' gutterBottom>
          当前活动持续时间:{formatTime()}
        </Typography>
      )}
    </Container>
  )
}

修改后每次只会存在一个定时器实例,切换currentActivityOn为false时所有定时器都会被清除,不会再出现每秒多次打印、定时器无法终止的问题。

内容的提问来源于stack exchange,提问作者Ben in CA

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 06:06:03