React组件里用定时器导致内存泄漏怎么优化?

UE丶雪琪 阅读 52

我在写一个实时刷新数据的React组件,发现组件卸载后计数器还在跑,内存占用越来越高。代码是这样写的:


function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prev => prev + 1); // 这里感觉有问题
    }, 1000);
    return () => clearInterval(timer); // 清理函数好像没生效?
  }, []);
  return 
计数:{count}
; }

虽然写了清理函数,但页面切换后控制台还持续输出计数。用React DevTools发现组件卸载后,闭包里仍然保留着count状态引用。尝试过把依赖数组改成[count]但导致无限循环,该怎么正确处理闭包引用避免内存泄漏呢?

我来解答 赞 6 收藏
二维码
手机扫码查看
2 条解答
Dev · 振莉
你这个情况很典型,定时器没清理干净是因为闭包引用导致的内存泄漏。代码里写了 clearInterval 是对的,但得确保它真能执行到。

首先你写的清理函数其实没问题,useEffect 返回的函数会在组件卸载时调用,清除定时器。但如果你在开发环境用的是 React 18 的严格模式,useEffect 会故意执行两次(mount + unmount + mount),这可能会让你误以为清理没生效——其实是清了又重新开了一个。你可以先关掉严格模式验证一下真实行为。

至于你说的“闭包保留 count 引用”,这不是内存泄漏的根本原因。真正的问题是:只要定时器还在跑,回调函数就一直被持有,里面的 setCount 就不会释放,进而持有了组件作用域,导致本该回收的组件实例无法 GC。

解决方法很简单:

useRef 来标记组件是否挂载,避免在组件卸载后还去更新状态。虽然定时器清了,但在最后一次回调和清理之间可能仍有微小的时间窗口,触发 setCount 导致报错或内存占用。

修改后的代码:

function Counter() {
const [count, setCount] = useState(0);
const mounted = useRef(false);

useEffect(() => {
mounted.current = true;
const timer = setInterval(() => {
if (!mounted.current) return;
setCount(prev => prev + 1);
}, 1000);

return () => {
mounted.current = false;
clearInterval(timer);
};
}, []);

return
计数:{count}
;
}


这样即使定时器回调延迟执行,也会因为 mounted.current 为 false 而跳过 setCount,避免无效状态更新。

另外,如果你是在做实时数据刷新,比如轮询 API 调用,建议别用 setInterval,改用递归 setTimeout,防止请求堆积。例如:

useEffect(() => {
let cancel = false;

const poll = () => {
fetch('/api/data').then(res => {
if (!cancel) {
// 处理数据
setCount(c => c + 1);
setTimeout(poll, 1000); // 下一轮
}
});
};

poll();

return () => {
cancel = true;
};
}, []);


总结:你的清理逻辑是对的,但要防开发模式双执行、加挂载标记、必要时换成递归定时避免并发问题。这才是生产级写法。
点赞 4
2026-02-11 18:20
东方一鸣
你这问题挺常见的,内存泄漏就是清理函数没生效导致的。试试这样改:

function Counter() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
let isMounted = true; // 加个标记
const timer = setInterval(() => {
if (isMounted) { // 卸载前不再执行
setCount(prev => prev + 1);
}
}, 1000);
return () => {
clearInterval(timer);
isMounted = false; // 卸载时设置为false
};
}, []);
return <div>计数:{count}</div>;
}


我之前这样搞的,基本没问题了。
点赞 12
2026-02-01 11:04