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

如何将React-Virtualized与Material UI/Bootstrap Grid结合实现虚拟化?

结合MUI Grid实现虚拟化的两种方案

你的核心问题是当前虚拟化组件基于单列表格(List)实现,无法适配MUI Grid的流式响应式布局。以下是两种可行的解决方案,既能保留MUI Grid的响应式特性,又能实现虚拟化:

方案一:使用react-window的多列Grid组件

react-window提供了FixedSizeGrid/VariableSizeGrid组件,支持多列虚拟化,可配合MUI Grid Item实现响应式布局。

实现代码

import { FixedSizeGrid as Grid } from 'react-window';
import { useTheme, useMediaQuery } from '@mui/material';
import { useRef, useEffect, useState } from 'react';

const VirtualizedGrid = ({ children }) => {
  const theme = useTheme();
  const containerRef = useRef<HTMLDivElement>(null);
  const [columnCount, setColumnCount] = useState(1);

  // 根据窗口宽度动态调整列数,匹配你的MUI Grid断点配置
  useEffect(() => {
    const updateColumnCount = () => {
      if (!containerRef.current) return;
      const width = containerRef.current.offsetWidth;
      const breakpoints = theme.breakpoints.values;
      
      if (width < breakpoints.sm) setColumnCount(1);    // xs断点:每行1个
      else if (width < breakpoints.md) setColumnCount(2); // sm断点:每行2个
      else if (width < breakpoints.xl) setColumnCount(4); // md断点:每行4个
      else setColumnCount(5);                           // xl断点:每行5个
    };

    updateColumnCount();
    window.addEventListener('resize', updateColumnCount);
    return () => window.removeEventListener('resize', updateColumnCount);
  }, [theme.breakpoints.values]);

  const rowCount = Math.ceil(children.length / columnCount);
  // 可根据实际卡片高度调整,若高度不固定可改用VariableSizeGrid配合动态测量
  const itemHeight = 300;

  return (
    <div ref={containerRef} style={{ height: '100%', width: '100%' }}>
      <Grid
        columnCount={columnCount}
        columnWidth={(containerWidth) => {
          // 计算每列宽度,自动扣除gap间距
          const gap = containerWidth < theme.breakpoints.values.lg ? theme.spacing(2) : theme.spacing(2.5);
          return (containerWidth - gap * (columnCount - 1)) / columnCount;
        }}
        rowCount={rowCount}
        rowHeight={itemHeight}
        height="100%"
        width="100%"
      >
        {({ columnIndex, rowIndex, style }) => {
          const itemIndex = rowIndex * columnCount + columnIndex;
          if (itemIndex >= children.length) return null;
          const item = children[itemIndex];
          
          return (
            <div style={{ ...style, padding: theme.spacing(2) }}>
              {item}
            </div>
          );
        }}
      </Grid>
    </div>
  );
};

使用方式

替换原有的VirtualizedScroll组件即可,外层MUI Grid容器保留原配置:

<Grid
  maxWidth='xl'
  height='100%'
  container
  direction='row'
  justifyContent='center'
  alignItems='stretch'
  py={4}
  px={{ xs: 2, lg: 0 }}
  sx={{ border: '1px solid red' }}
>
  <VirtualizedGrid>
    {userInventoryItems?.map((item) => (
      <Grid
        item
        xs={8}
        sm={5}
        md={3}
        xl={2.2}
        key={item.ItemInstanceId}
        sx={{ border: '1px solid blue' }}
      >
        {/* ItemCard内容保持不变 */}
      </Grid>
    ))}
  </VirtualizedGrid>
</Grid>

方案二:虚拟化整行MUI Grid容器

将每行的多个Grid Item打包成一个虚拟化行,每行是一个完整的MUI Grid容器,完全保留MUI的响应式布局逻辑。

实现代码

import { List, AutoSizer } from 'react-virtualized';
import { useTheme, useMediaQuery } from '@mui/material';
import { useEffect, useState } from 'react';

const VirtualizedRowGrid = ({ children }) => {
  const theme = useTheme();
  const [itemsPerRow, setItemsPerRow] = useState(1);

  // 根据断点调整每行显示的Item数量
  useEffect(() => {
    const updateItemsPerRow = () => {
      const breakpoints = theme.breakpoints.values;
      if (window.innerWidth < breakpoints.sm) setItemsPerRow(1);
      else if (window.innerWidth < breakpoints.md) setItemsPerRow(2);
      else if (window.innerWidth < breakpoints.xl) setItemsPerRow(4);
      else setItemsPerRow(5);
    };

    updateItemsPerRow();
    window.addEventListener('resize', updateItemsPerRow);
    return () => window.removeEventListener('resize', updateItemsPerRow);
  }, [theme.breakpoints.values]);

  const rowCount = Math.ceil(children.length / itemsPerRow);
  // 若每行高度不固定,可改用CellMeasurer动态测量高度
  const rowHeight = 300;

  return (
    <AutoSizer>
      {({ width, height }) => (
        <List
          width={width}
          height={height}
          rowCount={rowCount}
          rowHeight={rowHeight}
          rowRenderer={({ key, index, style }) => {
            const startIdx = index * itemsPerRow;
            const endIdx = Math.min(startIdx + itemsPerRow, children.length);
            const rowItems = children.slice(startIdx, endIdx);

            return (
              <div key={key} style={style}>
                <Grid
                  container
                  direction='row'
                  justifyContent='center'
                  alignItems='stretch'
                  gap={{ xs: 2, lg: 2.5 }}
                >
                  {rowItems}
                </Grid>
              </div>
            );
          }}
        />
      )}
    </AutoSizer>
  );
};

使用方式

外层MUI Grid容器可简化(仅保留padding和maxWidth),替换原虚拟化组件:

<Grid
  maxWidth='xl'
  height='100%'
  py={4}
  px={{ xs: 2, lg: 0 }}
  sx={{ border: '1px solid red' }}
>
  <VirtualizedRowGrid>
    {userInventoryItems?.map((item) => (
      <Grid
        item
        xs={8}
        sm={5}
        md={3}
        xl={2.2}
        key={item.ItemInstanceId}
        sx={{ border: '1px solid blue' }}
      >
        {/* ItemCard内容保持不变 */}
      </Grid>
    ))}
  </VirtualizedRowGrid>
</Grid>

方案对比

  • 方案一:性能更优,适合超大量数据;需手动计算列宽,响应式逻辑需自行维护。
  • 方案二:完全复用MUI Grid的响应式规则,无需额外计算;但每行高度需统一(或额外做动态测量),虚拟化精度依赖行高配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 08:52:03