react-window-infinite-loader搭配FixedSizeGrid时loadMoreItems不触发问题
问题:react-window FixedSizeGrid 结合 InfiniteLoader 时 loadMoreItems 不触发
使用 react-window 的 <FixedSizeGrid> 实现虚拟化列表,搭配 react-window-infinite-loader 的 <InfiniteLoader> 做无限滚动,但遇到 loadMoreItems 完全不触发的问题。已按文档配置 limit=10、threshold=5,但无限滚动失效,预期滚动到阈值位置时自动调用 loadMoreItems 加载更多数据。
相关代码
虚拟化列表组件
import { PropTypes } from 'prop-types'; import { FixedSizeGrid } from 'react-window'; import InfiniteLoader from 'react-window-infinite-loader'; import AutoSizer from 'react-virtualized-auto-sizer'; import './store_projects.scss'; import { useCallback } from 'react'; import useMediaQuery from '../../hooks/useMediaQuery'; import { StoreMediaQueries } from '../../constants'; const { mediaQueries } = StoreMediaQueries; const VirtualizedInfiniteList = ({ items, hasMore, loadMoreItems, threshold, children, isLoading, }) => { const activeMediaQuery = useMediaQuery(mediaQueries); const { gridColumnCount = 1, gridRowHeight } = activeMediaQuery?.mediaQuery ?? {}; const isItemLoaded = (index) => !hasMore || index < items.length; const loadMore = isLoading ? () => {} : loadMoreItems; const getIndexFromGridPosition = useCallback((col, row, columnCount) => { const index = (row * columnCount) + col; return index; }, []); const itemCount = hasMore ? Math.ceil(items.length / gridColumnCount) + 1 : items.length; return ( <AutoSizer> {({ height, width }) => ( <InfiniteLoader isItemLoaded={isItemLoaded} itemCount={itemCount} loadMoreItems={loadMore} threshold={threshold} > {({ onItemsRendered, ref }) => ( <FixedSizeGrid className="virtualized-grid-wrapper" columnCount={gridColumnCount} columnWidth={width / gridColumnCount} height={height} ref={ref} rowCount={Math.ceil(items.length / gridColumnCount)} rowHeight={gridRowHeight} width={width} onItemsRendered={onItemsRendered} > {({ columnIndex, rowIndex, style }) => { const index = getIndexFromGridPosition(columnIndex, rowIndex, gridColumnCount); if (!items || items.length === 0 || index >= items.length) { return null; } return ( <div className="store-project-card-wrapper" style={style}> {children(items[index])} </div> ); }} </FixedSizeGrid> )} </InfiniteLoader> )} </AutoSizer> ); }; VirtualizedInfiniteList.propTypes = { items: PropTypes.shape([]).isRequired, hasMore: PropTypes.bool, loadMoreItems: PropTypes.func, threshold: PropTypes.number, children: PropTypes.func.isRequired, isLoading: PropTypes.bool, }; VirtualizedInfiniteList.defaultProps = { hasMore: false, loadMoreItems: () => {}, threshold: 5, isLoading: false, }; export default VirtualizedInfiniteList;
loadMoreItems 函数
const loadMoreItems = useCallback(() => { if (!isLoading && hasMore) { setProject((prev) => ({ ...prev, isLoading: true })); fetchMore({ variables: { limit: allProjectsLimit, offset: list.length, }, updateQuery: (prev, { fetchMoreResult: { filterProjectsForAdmin: storeProjects } }) => { if (!storeProjects.length) { return prev; } setProject((prevProjects) => ({ list: [...prevProjects.list, ...storeProjects], hasMore: storeProjects.length >= allProjectsLimit, isLoading: false, })); return null; }, }); } }, [fetchMore, hasMore, isLoading, list.length]);
问题原因与修复方案
核心问题
itemCount与isItemLoaded逻辑不匹配:InfiniteLoader 的itemCount是按总行数计算,但isItemLoaded却用了列表项的索引判断,导致加载触发逻辑混乱。rowCount未预留加载占位行:FixedSizeGrid 的行数未包含加载占位行,滚动到底部时无法触发放载阈值。loadMore函数被错误替换:加载中时直接替换为空函数,导致 InfiniteLoader 无法感知加载状态。
修复步骤
1. 修正 InfiniteLoader 的核心逻辑
// 计算总行数,hasMore 时加1作为加载占位行 const totalRows = Math.ceil(items.length / gridColumnCount); const itemCount = hasMore ? totalRows + 1 : totalRows; // 基于行索引判断是否加载完成 const isItemLoaded = (rowIndex) => { // 最后一行且还有更多数据,标记为未加载 if (hasMore && rowIndex === totalRows) { return false; } // 当前行的起始项索引存在则已加载 const startIndex = rowIndex * gridColumnCount; return startIndex < items.length; };
2. 修正 FixedSizeGrid 的行数
<FixedSizeGrid // ...其他属性 rowCount={itemCount} // 用修正后的总行数,包含占位行 >
3. 移除错误的 loadMore 替换逻辑
直接传入 props 的 loadMoreItems,让函数内部处理加载状态:
<InfiniteLoader isItemLoaded={isItemLoaded} itemCount={itemCount} loadMoreItems={loadMoreItems} // 直接使用原函数 threshold={threshold} >
4. 添加加载占位行渲染
{({ columnIndex, rowIndex, style }) => { const index = getIndexFromGridPosition(columnIndex, rowIndex, gridColumnCount); // 渲染加载占位行 if (hasMore && rowIndex === totalRows) { return ( <div className="store-project-card-wrapper loading" style={style}> <div>加载中...</div> </div> ); } if (!items || items.length === 0 || index >= items.length) { return null; } return ( <div className="store-project-card-wrapper" style={style}> {children(items[index])} </div> ); }}
额外注意事项
- 确保
loadMoreItems中hasMore状态更新正确:当返回数据长度小于limit时,需将hasMore设为false,避免无限触发加载。 - 检查
useMediaQuery返回的网格参数是否正确,确保布局尺寸计算无误,避免滚动区域判断错误。
内容的提问来源于stack exchange,提问作者Ayush Bharadva
相关产品推荐
相关产品推荐

