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

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]);

问题原因与修复方案

核心问题

  1. itemCount 与 isItemLoaded 逻辑不匹配:InfiniteLoader 的 itemCount 是按总行数计算,但 isItemLoaded 却用了列表项的索引判断,导致加载触发逻辑混乱。
  2. rowCount 未预留加载占位行:FixedSizeGrid 的行数未包含加载占位行,滚动到底部时无法触发放载阈值。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 02:19:57