如何在MUI DataGrid中实现无限滚动(保留原组件)
解决MUI DataGrid结合GraphQL useLazyQuery的无限滚动问题
核心问题分析
- 当前
setStyles直接替换数据而非追加,导致新页数据覆盖旧数据 useLazyQuery的变量未动态更新,page变化时无法传递正确的cursor- 缺少滚动到底部触发加载下一页的逻辑
分步解决方案
1. 调整数据更新逻辑(StylesHOC)
- 重置搜索条件时清空现有数据,新页数据追加到现有数组
- 修改
useLazyQuery的onCompleted回调,改为追加数据:
onCompleted: (data) => { const newCursor = data.styles?.edges.at(-1)?.cursor; if (newCursor && !cursors.map((c) => c.cursor).includes(newCursor)) { setCursors(prev => [...prev, { pageIndex: page + 1, cursor: newCursor }]); } totalCountDispatch({ type: 'update_totalCount', newTotalCount: data.styles?.totalCount ?? 0 }); // 追加新数据而非替换 setStyles(prev => [ ...prev, ...(data.styles?.edges .map((edge) => edge.node) .filter((node) => !pinnedStyles.includes(node.styleNumber)) || []) ]); },
- 在重置逻辑的
useEffect中添加清空数据的操作:
useEffect(() => { setPage(0); setCursors([{ pageIndex: 0, cursor: '' }]); setStyles([]); // 重置时清空现有数据 if (selectedBrands.length !== 0) { // 调用getStyles时传入最新变量 getStyles({ variables: { brandNumber: selectedBrands[0], collectionNumber: collectionsFilter?.collectionNumber, productLineNumber: productLinesFilter?.productLineNumber, purchaseDivisionNumber: purchaseDivisionsFilter?.purchaseDivisionNumber, generalQuery: searchQuery, pagination: { first: size, after: '' }, }, }); } }, [selectedBrands, searchQuery, collectionsFilter, productLinesFilter, purchaseDivisionsFilter, getStyles]);
2. 动态传递GraphQL查询变量
将useLazyQuery的变量从初始配置中移除,改为调用时传入,确保每次查询都使用最新的page和cursor:
// 重新定义useLazyQuery,移除静态variables const [getStyles, { loading: getStylesQueryLoading, error: getStylesQueryError }] = useLazyQuery( graphQlService.getAllStyles, { pollInterval: 0, fetchPolicy: 'network-only', context: { clientName: GraphClientNames['blabla'] }, // onCompleted和onError保持修改后的逻辑 // ... } );
3. 实现加载下一页方法并注入Context
在StylesHOC中添加loadNextPage方法,用于触发下一页数据加载,并注入到Context中供DataGrid组件使用:
const loadNextPage = () => { const nextPage = page + 1; // 检查是否还有未加载的数据 if (!getStylesQueryLoading && styles.length < totalCount) { setPage(nextPage); getStyles({ variables: { brandNumber: selectedBrands[0], collectionNumber: collectionsFilter?.collectionNumber, productLineNumber: productLinesFilter?.productLineNumber, purchaseDivisionNumber: purchaseDivisionsFilter?.purchaseDivisionNumber, generalQuery: searchQuery, pagination: { first: size, after: cursors.find(c => c.pageIndex === nextPage)?.cursor ?? cursors.at(-1)?.cursor ?? '', }, }, }); } }; // 更新contextValue,加入loadNextPage const contextValue = useMemo( () => ({ styles, isStylesLoading: getStylesQueryLoading, stylesError: getStylesQueryError, totalCount, cursors, size, page, setPage, updateStyles, loadNextPage, // 新增加载下一页方法 }), [cursors, getStylesQueryError, getStylesQueryLoading, page, styles, totalCount, updateStyles, loadNextPage] );
4. 配置DataGrid触发无限滚动
在DataGrid组件中添加滚动监听,当滚动接近底部时触发加载下一页:
<DataGrid rows={tableRows} columns={columns} loading={isStylesLoading || !tableRows} localeText={localeText} rowCount={totalCount} getRowClassName={(params: GridRowParams): string => { const isPinnedRow = params.row.id.includes('pinned'); return `${classes.row} ${isPinnedRow ? classes.pinnedRow : ''}`; }} rowHeight={64} paginationMode="server" pageSize={size} page={page} // 绑定当前页码 classes={{ root: classes.root, columnHeaders: classes.columnHeaders, columnHeaderTitle: classes.columnHeaderTitle, cell: classes.cell, columnSeparator: classes.columnSeparator, footerContainer: classes.footerContainer, }} components={{ LoadingOverlay, Pagination: () => null, }} componentsProps={{ row: { onMouseEnter: handleRowMouseEnter, onMouseLeave: handleRowMouseLeave, }, }} disableSelectionOnClick disableColumnMenu onCellClick={handleCellClick} // 添加滚动监听触发加载下一页 onScroll={(event) => { const grid = event.currentTarget; // 距离底部100px时触发加载 if (grid.scrollTop + grid.clientHeight >= grid.scrollHeight - 100) { loadNextPage(); } }} />
关键注意事项
- 避免重复加载:在
loadNextPage中判断当前是否处于加载状态,防止多次触发请求 - 边界处理:检查
styles.length < totalCount,确保没有超出总数据量 - 变量依赖:确保
useEffect和loadNextPage的依赖数组包含所有相关变量,避免闭包问题
内容的提问来源于stack exchange,提问作者Jdeveloper
相关产品推荐
相关产品推荐

