一、什么是内存泄漏?

内存泄漏就像你租了房子却忘了退租,房东一直收不到房子,你还得继续付租金。在React中,组件卸载后,某些资源没有被正确释放,导致内存占用越来越高,最终可能让应用变卡甚至崩溃。

举个生活中的例子:你打开10个浏览器标签页却从不关闭,电脑越来越慢——这就是典型的内存泄漏。React应用中,常见于事件监听、定时器、全局变量等场景。

二、React内存泄漏的常见场景

1. 未清理的事件监听

// 技术栈:React + TypeScript
import React, { useEffect } from 'react';

const LeakyComponent = () => {
  useEffect(() => {
    const handleClick = () => console.log('点击事件泄漏了!');
    document.addEventListener('click', handleClick);
    
    // 错误示范:忘记移除监听
    // return () => document.removeEventListener('click', handleClick);
  }, []);

  return <div>试试点击页面其他地方</div>;
};

注释:组件卸载时未移除事件监听,导致每次重新加载组件都会叠加监听器。

2. 未清除的定时器

// 技术栈:React Hooks
const TimerLeak = () => {
  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log('定时器在后台运行...');
    }, 1000);

    // 正确做法:清除定时器
    return () => clearInterval(intervalId);
  }, []);
};

注释:如果忘记clearInterval,即使组件卸载,定时器仍会持续执行。

3. 异步请求未取消

// 技术栈:React + Axios
useEffect(() => {
  let isMounted = true;
  axios.get('/api/data').then(response => {
    if (isMounted) setData(response.data); // 只有组件挂载时才更新状态
  });

  return () => { isMounted = false; }; // 通过标志位避免更新已卸载组件
}, []);

注释:如果组件卸载后请求完成,直接setState会触发React警告。

三、高级场景与解决方案

1. 使用Ref管理可变值

// 技术栈:React useRef
const SafeComponent = () => {
  const isActive = useRef(true);

  useEffect(() => {
    fetchData().then(data => {
      if (isActive.current) updateState(data);
    });

    return () => { isActive.current = false; };
  }, []);
};

注释useRef在组件生命周期内持久化,适合存储易变状态。

2. 第三方库的内存泄漏

比如使用D3.js时:

useEffect(() => {
  const chart = d3.select('#chart').drawComplexChart();
  return () => chart.cleanup(); // 必须调用库提供的清理方法
}, []);

关联技术:某些图形库(如D3、Three.js)需要手动释放GPU资源。

四、工具链与调试技巧

  1. Chrome DevTools

    • 使用Memory面板拍摄堆快照,对比操作前后的内存差异。
    • 查找分离的DOM节点(Detached DOM tree)。
  2. React StrictMode
    在开发环境下会重复调用useEffect,帮助提前发现清理逻辑问题。

  3. ESLint规则
    启用exhaustive-deps规则,强制声明所有依赖项:

    "rules": {
      "react-hooks/exhaustive-deps": "warn"
    }
    

五、最佳实践总结

  1. 黄金法则:每个useEffect都对应一个清理函数。
  2. 代码审查:重点检查异步操作、全局对象和第三方库集成。
  3. 性能监控:生产环境接入Sentry或LogRocket捕获内存异常。

注意事项

  • 避免在全局对象(如window)上挂载属性。
  • 类组件中的componentWillUnmount同样需要清理逻辑。

技术优缺点

  • 手动清理更可控,但容易遗漏。
  • 自动垃圾回收不处理“逻辑泄漏”(如未取消的订阅)。