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

Material React Table虚拟化与行展开功能整合问题求助

Material React Table 无限滚动与行展开功能整合问题解决

问题核心

在结合Material React Table的无限滚动(虚拟化)和行展开折叠功能时,展开父行后子行无法正常渲染,本质是数据管理与虚拟化渲染的协同逻辑出现冲突:

  1. 手动用completeData.current缓存数据导致useInfiniteQuery的数据集不完整,getSubRows无法找到子行
  2. 展开行时未正确触发数据更新,或数据未被纳入虚拟化表格的数据集
  3. 虚拟化组件未在展开状态变化时重新计算行布局

代码修正方案

1. 重构useInfiniteQuery数据逻辑

移除手动缓存,让useInfiniteQuery统一管理顶级行和子行数据,利用queryKey中的expanded状态自动触发数据更新:

const { data, fetchNextPage, isError, isFetching, isLoading } = useInfiniteQuery({
  queryKey: ['table-data', columnFilters, expanded, sorting],
  queryFn: async ({ pageParam = 0 }) => {
    const offset = pageParam * fetchSize;
    const preQueryFilters = props.preQueryRes.decompose()[0].rawData();
    let processedResp;

    // 判断是否处于展开状态
    const expandedRowEntry = Object.entries(expanded).find(([_, isExpanded]) => isExpanded);
    const isExpanding = !!expandedRowEntry;
    const expandedRowId = isExpanding ? expandedRowEntry[0].split('-').pop() : null;

    if (isExpanding && pageParam === 0) {
      // 展开时:重新获取已加载的所有顶级行 + 当前展开行的子行
      const loadedTopRowCount = (data?.pages.length || 1) * fetchSize;
      const topLevelResponse = await loadCubeData(
        addPreQueryResAsFilter(props.query, preQueryFilters.slice(0, loadedTopRowCount))
      );
      const topLevelData = processData(topLevelResponse);

      // 获取子行数据
      const [subDim, readyQuery] = addSubQueryFilter(props.query, expandedRowId);
      const subRowResponse = await loadCubeData(readyQuery);
      const subRowData = processData(subRowResponse, subDim);

      processedResp = [...topLevelData, ...subRowData];
    } else {
      // 正常无限滚动:加载下一页顶级行
      const response = await loadCubeData(
        addPreQueryResAsFilter(props.query, preQueryFilters.slice(offset, offset + fetchSize))
      );
      processedResp = processData(response);
    }

    return {
      data: processedResp,
      meta: { totalRowCount: preQueryFilters.length || 0 },
    };
  },
  initialPageParam: 0,
  getNextPageParam: (_lastGroup, groups) => {
    // 展开状态下禁止加载下一页,避免数据混乱
    const isExpanding = Object.values(expanded).some(Boolean);
    return isExpanding ? undefined : groups.length;
  },
  refetchOnWindowFocus: false,
});

2. 简化数据集生成

确保flatReportData包含所有已加载的顶级行和子行:

const flatReportData = useMemo(() => {
  return data?.pages?.flatMap(page => page.data) || [];
}, [data]);

3. 调整表格组件配置

  • 仅在非展开状态下触发无限滚动
  • 通知虚拟化组件在展开状态变化时重新计算布局:
// 新增:展开状态变化时重新测量虚拟行
useEffect(() => {
  rowVirtualizerInstanceRef.current?.measure();
}, [expanded]);

// 表格组件
<MaterialReactTable
  columns={tableColumns.current}
  data={flatReportData.filter(r => !r.parentId)}
  enablePagination={false}
  enableRowVirtualization
  getRowId={(originalRow) => originalRow.id}
  muiTableContainerProps={{
    ref: tableContainerRef,
    sx: { maxHeight: '600px' },
    onScroll: (event) => {
      // 仅在非展开状态下触发无限滚动加载
      const isExpanding = Object.values(expanded).some(Boolean);
      if (!isExpanding) {
        fetchMoreOnBottomReached(event.target);
      }
    },
  }}
  muiToolbarAlertBannerProps={isError ? { color: 'error', children: 'Error loading data' } : undefined}
  renderBottomToolbarCustomActions={() => (
    <Typography>
      Fetched {totalFetched} of {totalDBRowCount} total rows.
    </Typography>
  )}
  state={{
    expanded,
    columnFilters,
    isLoading,
    showAlertBanner: isError,
    showProgressBars: isFetching,
    sorting,
  }}
  rowVirtualizerInstanceRef={rowVirtualizerInstanceRef}
  rowVirtualizerOptions={{ overscan: 4 }}
  onColumnFiltersChange={setColumnFilters}
  onExpandedChange={setExpanded}
  enableTopToolbar={false}
  enableExpanding
  getRowCanExpand={(row) => row.original.level < levels.length}
  getSubRows={(row) => flatReportData.filter(r => r.parentId === row.id)}
  onSortingChange={setSorting}
  paginateExpandedRows={false}
  enableColumnFilters={false}
  enableColumnActions={false}
  enableFullScreenToggle={false}
  enableDensityToggle={false}
  initialState={{ density: 'spacious' }}
  enableHiding={false}
/>

关键最佳实践

  1. 统一数据管理:始终让useInfiniteQuery管理所有表格数据,避免手动缓存导致的数据不一致
  2. 状态驱动查询:利用queryKey中的expanded状态自动触发数据更新,无需手动判断
  3. 虚拟化适配:展开状态变化时强制虚拟器重新测量,确保子行布局正确
  4. 互斥操作限制:展开行时禁止无限滚动加载,避免顶级行与子行的数据冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 04:48:15