SetInterval Bug排查:模态框定时器首次正常,重渲染后加速异常
解决模态框倒计时多次打开后异常加速的问题
你的问题核心是多个定时器未被正确清除,导致每次打开模态框时都会新增一个定时器,多个定时器同时修改timeLeft,所以倒计时会越来越快。结合你的代码,主要有几个问题需要修正:
关键问题分析
- 清理函数注册错误:第一个
useEffect中,当!show时直接return,导致清理函数没有被React注册,定时器无法在模态框关闭时被清除。 - 定时器启动时机错误:每次
show变化时都会创建新定时器,包括模态框关闭的时候,导致残留定时器继续运行。 - 闭包导致的定时器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
相关产品推荐
相关产品推荐

