React组件中如何避免DOM引用导致的内存泄漏?

轩辕新艳 阅读 59

在开发一个动态加载的列表组件时,我给列表容器加了ref用来监听滚动,但发现组件卸载后内存 profiling 还能看到之前的实例残留。

已经用了useEffect的返回函数把ref.current设为null,但问题依旧存在。代码大致是这样的:


const listRef = useRef(null);

useEffect(() => {
  const handleScroll = () => {
    // 加载更多逻辑
  };
  listRef.current.addEventListener('scroll', handleScroll);
  return () => {
    listRef.current?.removeEventListener('scroll', handleScroll);
    listRef.current = null; // 这里是不是没用?
  };
}, []);

页面切换时控制台没报错,但任务管理器显示内存持续增长。是不是还有其他地方在引用DOM节点?或者设置ref.current=null的方式不对?

我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
博主一鸣
问题出在事件监听和引用清理上,你现在的写法效率不够高,而且确实可能存在隐式引用导致内存没释放干净。我直接给优化方案。

首先,listRef.current = null 这一行其实是多余的,React 的 useRef 本身不会阻止垃圾回收,真正的问题大概率是事件监听器没彻底清除干净,或者有其他地方持有了对 DOM 节点的引用。

优化后的代码如下:


const listRef = useRef(null);

useEffect(() => {
const handleScroll = () => {
// 加载更多逻辑
};

const currentList = listRef.current; // 提前解构,避免隐式引用
if (currentList) {
currentList.addEventListener('scroll', handleScroll);
}

return () => {
if (currentList) {
currentList.removeEventListener('scroll', handleScroll);
}
// 不需要手动设置 listRef.current = null
};
}, []);


这里的关键点是:把 listRef.current 解构到局部变量 currentList,确保在组件卸载时没有隐式的外部依赖。React 的 useRef 本身不会阻止 GC(垃圾回收),但如果你在闭包中直接用 listRef.current,可能会让事件监听器内部形成隐式引用链,导致 DOM 节点无法被回收。

另外,检查一下你的组件树,看看有没有其他地方也在操作这个 DOM 节点。比如父组件传下来的回调函数、第三方库之类的,都可能持有对 DOM 的引用。如果有的话,记得在组件卸载时一并清理掉。

最后提一句,别忘了用 React DevTools 或者 Chrome 的 Performance 工具再跑一遍 profiling,确认内存是否真的被释放了。有时候浏览器本身的 GC 延迟也会让你误以为内存泄漏了,实际上只是还没触发回收而已。

效率更高,也更干净的做法就是这些了,赶紧试试吧。
点赞 3
2026-02-14 21:24
IT人树遥
问题出在事件监听器那里,虽然你写了清除逻辑,但 handleScroll 是个新的匿名函数实例,每次组件渲染都会生成一个新的。这样导致你在添加和移除监听器时,实际上操作的不是同一个函数,清除不干净就容易引发内存泄漏。

先检查一下 useEffect 里的 handleScroll 定义位置。你需要把事件处理函数提取到外部,确保它在整个组件生命周期内是稳定的。用 useCallback 包裹一下就行。

修改后的代码大概是这样的:
const listRef = useRef(null);

const handleScroll = useCallback(() => {
// 加载更多逻辑
}, []);

useEffect(() => {
const currentRef = listRef.current;
if (currentRef) {
currentRef.addEventListener('scroll', handleScroll);
return () => {
currentRef.removeEventListener('scroll', handleScroll);
};
}
}, [handleScroll]);

// ref.current = null 这行其实没必要,ref本身不会引起泄漏


另外,ref.current = null 这步确实没太大意义,因为 useRef 不会导致内存泄漏,真正的问题是未正确清理事件监听器。照着改完应该就能解决内存增长的问题了。如果还有问题,建议再看看其他地方有没有类似的监听器残留。
点赞 10
2026-02-01 06:06