React/MUI DataGrid留存旧行引用引发内存泄漏问题排查
React MUI DataGrid分页后旧对象内存泄漏问题解决
核心问题分析
你的问题本质是旧行对象被意外引用,无法被垃圾回收,主要由以下几点导致:
keepNonExistentRowsSelected属性强制DataGrid保留已移除行的选中状态,直接绑定旧行对象引用- 选中状态
selectedResources未在分页切换时清空,持续留存旧行引用 - 函数闭包问题导致旧状态/请求结果被意外持有
- 参数不匹配引发的逻辑隐性错误(CustomDataGrid调用
updatePage时传了2个参数,但Home组件的updatePage只接受1个)
具体解决方案
1. 移除keepNonExistentRowsSelected属性
这个属性是内存泄漏的核心诱因——它会让DataGrid保留所有曾经被选中过、但已不在当前rows数组中的行的状态,直接导致旧行对象无法被GC回收。删除DataGrid中的该属性:
<DataGrid // 移除这一行:keepNonExistentRowsSelected ...其他属性 />
2. 分页切换/数据更新时清空选中状态
在CustomDataGrid中,每当新数据加载完成(rows更新)或分页切换时,主动清空选中状态,彻底解除旧行的引用:
// 补全selectedResources的定义(你代码里漏了) const [selectedResources, setSelectedResources] = useState([]); // rows更新时清空选中状态 useEffect(() => { setloading(false); setSelectedResources([]); }, [rows]); // 分页切换时提前清空 onPageChange={(newPage) => { nextOrPrev.current = newPage > paginationModel.page ? "next" : "previous"; setSelectedResources([]); // 切换页面前清空选中 setPaginationModel({ ...paginationModel, page: newPage }); }}
3. 修复闭包与依赖问题
- 给Home组件的
updatePage添加useCallback包裹,避免每次渲染创建新函数,同时确保依赖正确:
import { useCallback } from 'react'; const Home = () => { // ...其他状态 const updatePage = useCallback(async (resourceType, nextOrPrev) => { // 原函数逻辑调整为使用resourceType参数 if (nextOrPrev == "next") { if (!nextPageLink[resourceType]) return; const result = await getPageData(nextPageLink[resourceType]); setNextPageLink(prev => ({ ...prev, [resourceType]: result["nextLink"] })); setPrevPageLink(prev => ({ ...prev, [resourceType]: result["prevLink"] })); setResults(prev => ({ ...prev, [resourceType]: result["data"] })); } else if (nextOrPrev == "previous") { if (!prevPageLink[resourceType]) return; const result = await getPageData(prevPageLink[resourceType]); setNextPageLink(prev => ({ ...prev, [resourceType]: result["nextLink"] })); setPrevPageLink(prev => ({ ...prev, [resourceType]: result["prevLink"] })); setResults(prev => ({ ...prev, [resourceType]: result["data"] })); } }, [nextPageLink, prevPageLink]); // 依赖用到的状态 // ...渲染 };
- 修正CustomDataGrid中useEffect的依赖数组,匹配参数传递逻辑:
useEffect(() => { updatePage(resourceType, nextOrPrev.current); }, [paginationModel.page, updatePage, resourceType]);
4. 处理请求竞态(可选但推荐)
快速切换分页时,前一个未完成的请求可能在新请求之后返回,导致旧数据被存入状态,同时旧请求的结果对象会被引用。用AbortController取消未完成的请求:
const updatePage = useCallback(async (resourceType, nextOrPrev) => { const controller = new AbortController(); try { let url; if (nextOrPrev === "next") { if (!nextPageLink[resourceType]) return; url = nextPageLink[resourceType]; } else if (nextOrPrev === "previous") { if (!prevPageLink[resourceType]) return; url = prevPageLink[resourceType]; } // 假设getPageData支持传入signal参数 const result = await getPageData(url, { signal: controller.signal }); // 更新状态逻辑 setNextPageLink(prev => ({ ...prev, [resourceType]: result["nextLink"] })); setPrevPageLink(prev => ({ ...prev, [resourceType]: result["prevLink"] })); setResults(prev => ({ ...prev, [resourceType]: result["data"] })); } catch (error) { // 忽略请求被取消的错误 if (error.name !== "AbortError") throw error; } // 组件卸载或函数重新执行时取消请求 return () => controller.abort(); }, [nextPageLink, prevPageLink]);
验证方法
修改后,打开Chrome DevTools的Memory面板:
- 触发几次分页切换
- 手动触发GC(点击垃圾桶图标)
- 查看堆快照,确认旧的行对象不再被留存
内容的提问来源于stack exchange,提问作者Washbear
相关产品推荐
相关产品推荐

