You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React/MUI DataGrid留存旧行引用引发内存泄漏问题排查

React MUI DataGrid分页后旧对象内存泄漏问题解决

核心问题分析

你的问题本质是旧行对象被意外引用,无法被垃圾回收,主要由以下几点导致:

  1. keepNonExistentRowsSelected 属性强制DataGrid保留已移除行的选中状态,直接绑定旧行对象引用
  2. 选中状态selectedResources未在分页切换时清空,持续留存旧行引用
  3. 函数闭包问题导致旧状态/请求结果被意外持有
  4. 参数不匹配引发的逻辑隐性错误(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面板:

  1. 触发几次分页切换
  2. 手动触发GC(点击垃圾桶图标)
  3. 查看堆快照,确认旧的行对象不再被留存

内容的提问来源于stack exchange,提问作者Washbear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 05:47:05