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

React useEffect返回函数中clearTimeout失效问题排查

问题根源分析

你的问题出在两个关键地方:


1. 错误的状态更新方式导致timers数组未正确更新

你在setTimers里直接修改了React状态数组的引用:

setTimers((prev) => {
  prev.push(timer)
  return prev
});

React判断状态是否变化是基于浅比较,你直接对prev数组执行push(修改原数组),然后返回同一个引用,React会认为状态没有改变,因此不会更新timers的状态值。最终清理函数里的timers还是初始的空数组,自然无法清除任何定时器。


2. 闭包导致清理函数捕获的是旧的状态值

即使你正确更新了状态,因为你的useEffect依赖项是空数组,这个useEffect只会在组件首次渲染时执行,清理函数会捕获首次渲染时的timers状态值(也就是空数组)。后续对timers的状态更新不会影响清理函数里的变量引用,所以清理时依然无法访问到所有定时器ID。


修复方案

最可靠的方式是使用useRef存储定时器数组,因为useRef的current属性是可变的,不会触发组件重新渲染,而且清理函数能直接访问到最新的定时器列表:

import { useEffect, useRef } from 'react';

// 用useRef替代useState存储定时器
const timersRef = useRef<ReturnType<typeof setTimeout>[]>([]);

useEffect(() => {
  for (let index = 0; index < 12; index++) {
    const delay = (index + 1) * 10000;
    const timer = setTimeout(() => {
      checkProgress(id, setFieldValue, contractType, fileOCRStatus);
    }, delay);
    timersRef.current.push(timer);
  }

  // 清理函数
  return () => {
    timersRef.current.forEach(timer => clearTimeout(timer));
    // 清空数组,避免内存泄漏
    timersRef.current = [];
  };
}, []); // 根据实际业务场景,可添加id等相关依赖项

如果一定要用useState,必须确保每次更新都返回新数组(比如setTimers(prev => [...prev, timer])),但这种方式会因为闭包问题需要额外处理依赖,反而容易出错,因此优先选择useRef方案。

额外注意:如果checkProgress已经发起了API请求,clearTimeout只能取消尚未执行的定时器,无法终止已经发出的请求。如果需要取消正在进行的API调用,要使用AbortController来实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:55:28