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

React useEffect如何清除setInterval?倒计时按钮卸载时清理定时器方法

无限渲染根因
  • 你当前把定时器ID存在timer状态里,且该字段被加入了useEffect的依赖数组
  • useEffect每次执行前都会触发上一轮的清理函数,清理函数中执行setTimer(null)修改了状态,会再次触发useEffect执行,陷入死循环
  • 同时原有定时器逻辑存在闭包问题,定时器内读取的count是创建定时器时的快照,不是最新值,倒计时逻辑本身也存在缺陷
正确实现方案

将定时器ID存在useRef中即可解决,定时器ID不需要触发渲染,用ref存储不会触发重渲染,也不会产生闭包问题:

const CooldownButton = ({
  cooldown,
  classes,
  counterProps,
  ...props
}) => {
  const defaultClasses = useStyles();

  const [count, setCount] = useState(cooldown);
  // 用ref存定时器ID,不触发渲染,跨渲染周期保持最新值
  const timerRef = useRef(null);

  useEffect(() => {
    // 仅当倒计时大于0且没有正在运行的定时器时启动
    if (count > 0 && !timerRef.current) {
      timerRef.current = setInterval(() => {
        setCount(prevCount => {
          if (prevCount <= 1) {
            // 倒计时到0时自动清理定时器
            clearInterval(timerRef.current);
            timerRef.current = null;
            return 0;
          }
          return prevCount - 1;
        });
      }, 1000);
    }

    // 清理逻辑:组件卸载、count变化时自动执行
    return () => {
      if (timerRef.current) {
        clearInterval(timerRef.current);
        timerRef.current = null;
      }
    };
  // 依赖仅需要count,ref不需要加入依赖数组
  }, [count]);

  useUpdateEffect(() => {
    setCount(cooldown);
  }, [cooldown]);

  return (
    // ...
        <Typography
          size={24}
          className={clsx(defaultClasses.counter, classes?.counter)}
          {...counterProps}
        >
          {`${new Date(count * 1000)
            .toISOString()
            .substr(...(count >= 3600 ? [11, 8] : [14, 5]))}`}
        </Typography>
    // ...
  );
};
方案说明
  • 用useRef存储定时器ID,修改ref值不会触发组件重渲染,从根源避免了清理逻辑触发无限渲染的问题
  • 倒计时修改count时使用setCount的回调函数读取最新值,彻底解决闭包问题
  • 清理逻辑会在组件卸载时自动执行,完全覆盖类组件componentWillUnmount生命周期的清理需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 01:45:04