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

SetInterval Bug排查:模态框定时器首次正常,重渲染后加速异常

解决模态框倒计时多次打开后异常加速的问题

你的问题核心是多个定时器未被正确清除,导致每次打开模态框时都会新增一个定时器,多个定时器同时修改timeLeft,所以倒计时会越来越快。结合你的代码,主要有几个问题需要修正:

关键问题分析

  1. 清理函数注册错误:第一个useEffect中,当!show时直接return,导致清理函数没有被React注册,定时器无法在模态框关闭时被清除。
  2. 定时器启动时机错误:每次show变化时都会创建新定时器,包括模态框关闭的时候,导致残留定时器继续运行。
  3. 闭包导致的定时器ID丢失:直接在useEffect内声明intervalId,可能因为闭包问题导致后续无法正确清除旧定时器。

修正后的代码

import gsap from 'gsap'
import Image from 'next/future/image'
import { type FC, useEffect, useState, useRef } from 'react'
import { Transition } from 'react-transition-group'

import logo from '@/assets/brand/logo.svg'
import styles from './Modal.module.scss'

const Modal: FC<any> = ({ show, setShow }) => {
  const container = useRef<HTMLDivElement>(null)
  const q = gsap.utils.selector(container)
  const [timeLeft, setTimeLeft] = useState(10)
  const intervalRef = useRef<NodeJS.Timeout | null>(null) // 用ref存储定时器ID

  useEffect(() => {
    // 仅当模态框显示时启动定时器
    if (show) {
      setTimeLeft(10) // 打开时重置倒计时
      intervalRef.current = setInterval(() => {
        setTimeLeft(prev => {
          const newTime = prev - 1
          // 倒计时结束后直接清除定时器
          if (newTime <= 0) {
            clearInterval(intervalRef.current!)
            intervalRef.current = null
          }
          return newTime
        })
      }, 1000)
    }

    // 注册清理函数,组件卸载或show变化时清除定时器
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current)
        intervalRef.current = null
      }
    }
  }, [show])

  useEffect(() => {
    if (timeLeft <= 0) {
      setShow(false)
    }
  }, [setShow, timeLeft])

  const onEnter = () => {
    gsap.set(container.current, { opacity: 0 })
    gsap.set(q('*'), { opacity: 0, y: 8 })
  }

  const onEntering = () => {
    gsap.timeline()
      .to(container.current, { opacity: 1, duration: 0.3 })
      .to(q('*'), { opacity: 1, y: 0, stagger: 0.08 })
  }

  const onExiting = () => {
    gsap.to(container.current, { autoAlpha: 0, duration: 0.3 })
  }

  return (
    <Transition
      in={show}
      timeout={600}
      mountOnEnter={true}
      unmountOnExit={true}
      nodeRef={container}
      onEnter={onEnter}
      onEntering={onEntering}
      onExiting={onExiting}
    >
      <div ref={container} className={styles.container}>
        <Image 
          src={logo} 
          alt="Loopspeed" 
          width={214} 
          height={44} 
          style={{ objectFit: 'contain' }} 
        />
        <p>Blah Blah </p>
        <p>This modal will close in <strong>{timeLeft}</strong> seconds.</p>
      </div>
    </Transition>
  )
}

export default Modal

核心修改点

  • 用useRef存储定时器ID:避免闭包导致的定时器ID丢失,确保每次都能正确清除旧定时器。
  • 修正清理函数:直接返回清理函数,无论show状态如何,组件卸载或show变化时都会清除定时器。
  • 仅在show为true时启动定时器:同时打开时重置timeLeft为10,确保每次打开都是从10秒开始。
  • 倒计时结束时主动清除定时器:避免定时器在模态框关闭后仍残留运行。

这样修改后,每次打开模态框都会创建一个全新的定时器,关闭时立即清除,不会出现多个定时器同时运行的情况,倒计时就能正常从10秒开始。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 16:55:22