如何将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
相关产品推荐
相关产品推荐

