react-window中FixedSizeGrid嵌套InfiniteLoader不触发loadMoreItems求助
解决react-window FixedSizeGrid搭配InfiniteLoader不触发加载更多的问题
你遇到的核心问题来自两个关键逻辑错误:
- isItemLoaded函数逻辑错误:原代码直接返回全局加载状态,而InfiniteLoader需要通过该函数判断指定索引的条目是否已加载,而非当前是否在加载中。
- Grid的rowCount设置错误:原代码用已加载产品数计算行数,导致InfiniteLoader误以为没有更多条目需要加载,自然不会触发
loadMoreItems。
代码修改方案
1. 修正isItemLoaded函数
将函数改为根据索引判断对应产品是否存在于已加载列表中:
isItemLoaded={(index) => !!products[index]}
2. 调整Grid的rowCount计算
用总产品数计算总行数,让Grid明确知道还有更多内容待加载:
rowCount={Math.ceil(TOTAL_PRODUCTS / columnCount)}
3. 处理未加载条目的渲染
因为rowCount基于总产品数,滚动时会提前渲染未加载的条目位置,需要添加占位内容:
const renderItem = ({ columnIndex, rowIndex, style }) => { const index = rowIndex * columnCount + columnIndex; const product = products[index]; // 产品未加载时显示占位 if (!product) { return ( <div style={style} key={index}> <Paper sx={{ m: 3, display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }} elevation={3}> <Typography>加载中...</Typography> </Paper> </div> ); } // 原有产品渲染逻辑 return ( <div style={style} key={product.id}> {/* 原代码内容 */} </div> ); };
4. 优化状态更新的闭包问题
在fetchProducts中使用函数式更新,避免因闭包导致的状态错误:
const fetchProducts = async (pageNumber) => { setIsLoading(true); try { const response = await axios.get( `http://localhost:5000/products?_page=${pageNumber}&_limit=${PAGE_SIZE}` ); const newProducts = response.data; // 用函数式更新获取最新状态 setProducts(prevProducts => [...prevProducts, ...newProducts]); setCurrentPage(prevPage => prevPage + 1); } catch (error) { console.error('Error fetching products:', error); } finally { setIsLoading(false); } };
完整修改后的代码
import React, { useEffect, useState } from "react"; import Image from 'next/image' import { FixedSizeGrid as Grid } from 'react-window' import InfiniteLoader from 'react-window-infinite-loader' import Paper from '@mui/material/Paper'; import { styled } from '@mui/material/styles'; import axios from 'axios'; import { Box, Stack, Typography } from '@mui/material'; // Custom imports import { poppinsBold } from '../../shared/appfonts'; // Variables used for infinite virtualized list const PAGE_SIZE = 20; const TOTAL_PRODUCTS = 1000; const columnCount = 3; // Number of columns (items per row) const ProductList = () => { const [products, setProducts] = useState([]); const [isLoading, setIsLoading] = useState(false); const [currentPage, setCurrentPage] = useState(1); const TitleTypography = styled(Typography)({ fontFamily: poppinsBold.style.fontFamily, color: 'black', }); const MediumTypography = styled(Typography)({ fontFamily: poppinsBold.style.fontFamily, color: 'black', }); const fetchProducts = async (pageNumber) => { setIsLoading(true); try { const response = await axios.get( `http://localhost:5000/products?_page=${pageNumber}&_limit=${PAGE_SIZE}` ); const newProducts = response.data; setProducts(prev => [...prev, ...newProducts]); setCurrentPage(prev => prev + 1); } catch (error) { console.error('Error fetching products:', error); } finally { setIsLoading(false); } }; const loadMoreItems = () => { if (!isLoading && products.length < TOTAL_PRODUCTS) { fetchProducts(currentPage); } }; useEffect(() => { fetchProducts(currentPage); }, []); const renderItem = ({ columnIndex, rowIndex, style }) => { const index = rowIndex * columnCount + columnIndex; const product = products[index]; if (!product) { return ( <div style={style} key={index}> <Paper sx={{ m: 3, display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }} elevation={3}> <Typography>加载中...</Typography> </Paper> </div> ); } return ( <div style={style} key={product.id}> <Paper sx={{ m: 3 }} elevation={3}> <Stack display={'flex'} direction={'row'}> <Image width={120} height={150} src={ 'https://images.pexels.com/photos/10863290/pexels-photo-10863290.jpeg?auto=compress&cs=tinysrgb&w=600' } alt="产品图片" style={{ objectFit: 'fill' }} /> <Box display={'flex'} alignItems={'center'} flexDirection={'column'} justifyContent={'center'} width={'100%'} > <TitleTypography>{product.name}</TitleTypography> <MediumTypography>${product.price}</MediumTypography> </Box> </Stack> </Paper> </div> ); }; return ( <Box> <InfiniteLoader isItemLoaded={(index) => !!products[index]} itemCount={TOTAL_PRODUCTS} loadMoreItems={loadMoreItems} > {({ onItemsRendered, ref }) => ( <Grid height={window.innerHeight} width={window.innerWidth} columnCount={columnCount} columnWidth={window.innerWidth / columnCount} rowCount={Math.ceil(TOTAL_PRODUCTS / columnCount)} rowHeight={200} onItemsRendered={onItemsRendered} ref={ref} > {renderItem} </Grid> )} </InfiniteLoader> </Box> ) } export default ProductList;
替代方案
如果react-window的Grid组合InfiniteLoader仍有兼容问题,可以考虑:
- 使用
react-virtualized的Grid组件搭配其内置的无限滚动逻辑,生态更成熟。 - 手动实现滚动监听:给Grid容器添加
scroll事件,判断滚动位置接近底部时触发加载更多。
内容的提问来源于stack exchange,提问作者Vishwa
相关产品推荐
相关产品推荐

