一、内存泄漏是个什么鬼?
咱们做React开发的,最怕的就是应用用着用着越来越卡,最后直接崩溃。这种情况十有八九是内存泄漏在搞鬼。简单来说,内存泄漏就是该释放的内存没释放,就像你家水龙头没关紧,水一直在流,最后水费爆表。
在React里,常见的内存泄漏场景包括:事件监听没卸载、定时器没清除、全局变量滥用、大对象没释放等。这些都会导致内存占用越来越高,轻则卡顿,重则崩溃。
二、如何发现内存泄漏?
1. Chrome开发者工具是神器
打开Chrome DevTools,到Memory面板,用Heap Snapshot功能拍个快照。操作步骤:
- 打开你的React应用
- F12打开开发者工具
- 切换到Memory标签页
- 点击"Take heap snapshot"
- 操作你的应用
- 再拍一个快照
- 对比两个快照
2. 示例代码(React + TypeScript技术栈)
import React, { useEffect, useState } from 'react';
const LeakyComponent = () => {
const [data, setData] = useState<any>(null);
useEffect(() => {
// 错误示范:定时器没清理
const timer = setInterval(() => {
fetchData();
}, 1000);
// 正确做法应该这样:
// return () => clearInterval(timer);
const fetchData = async () => {
// 模拟大数据请求
const bigData = new Array(1000000).fill({
id: Math.random(),
content: 'This is some leaky data'
});
setData(bigData);
};
fetchData();
}, []);
return <div>内存泄漏示例组件</div>;
};
export default LeakyComponent;
这个组件有两个问题:
- 定时器没清理,会一直执行
- 每次请求都会创建超大数组,旧数据却没释放
三、常见内存泄漏场景及修复
1. 事件监听没卸载
import React, { useEffect } from 'react';
const EventListenerLeak = () => {
useEffect(() => {
const handleScroll = () => {
console.log('Scrolling...');
};
window.addEventListener('scroll', handleScroll);
// 忘记卸载事件监听
// 正确做法:
// return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div style={{ height: '200vh' }}>滚动我看看控制台</div>;
};
2. 订阅没取消
import React, { useEffect, useState } from 'react';
import { fromEvent } from 'rxjs';
const RxJsLeak = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// 创建可观察对象
const subscription = fromEvent(document, 'click')
.subscribe(() => {
setCount(c => c + 1);
});
// 忘记取消订阅
// 正确做法:
// return () => subscription.unsubscribe();
}, []);
return <div>点击次数: {count}</div>;
};
3. 大对象缓存问题
import React, { useEffect, useState } from 'react';
const BigDataCache = () => {
const [showData, setShowData] = useState(false);
// 这个大对象会一直存在内存中
const bigDataCache = new Array(1000000).fill({
id: Math.random(),
content: 'This is some cached data'
});
useEffect(() => {
// 模拟数据使用
if (showData) {
console.log(bigDataCache.length);
}
}, [showData]);
return (
<div>
<button onClick={() => setShowData(!showData)}>
{showData ? '隐藏数据' : '显示数据'}
</button>
</div>
);
};
四、高级检测与修复技巧
1. 使用React DevTools检测
React DevTools可以检测组件是否被正确卸载。如果组件已经不在DOM中,但还在内存中,那就是泄漏了。
2. 内存泄漏自动检测工具
可以集成why-did-you-render或memlab这样的工具到开发环境:
// 在项目入口文件添加
import { setConfig } from 'react-query';
setConfig({
// 开启内存泄漏检测
queries: {
staleTime: 30 * 1000,
cacheTime: 5 * 60 * 1000,
// 其他配置...
}
});
3. 使用WeakMap和WeakSet
import React, { useEffect } from 'react';
const WeakMapExample = () => {
useEffect(() => {
// 使用WeakMap而不是普通Map
const weakMap = new WeakMap();
const bigObject = { /* 大对象 */ };
weakMap.set(bigObject, 'some metadata');
// 当bigObject不再被引用时,会自动被垃圾回收
// 而普通Map会一直保持对键的强引用
}, []);
return <div>WeakMap示例</div>;
};
五、最佳实践总结
- 每个useEffect都要考虑清理函数
- 避免在全局作用域存储大对象
- 使用适当的缓存策略
- 定期进行内存检测
- 考虑使用不可变数据来避免意外引用
记住,内存泄漏就像房间里的垃圾,不及时清理就会越堆越多。养成良好的编码习惯,才能写出高性能的React应用。
评论