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

基于React-Query实现无限滚动后,如何用React-Window实现列表虚拟化?

解决React-Query无限滚动结合React-Window列表虚拟化的问题

核心思路

要解决DOM臃肿导致的性能问题,需将原一次性渲染所有文章的逻辑替换为React-Window的虚拟化列表——仅渲染当前视口内的文章,同时保留React-Query的分页数据管理能力。关键步骤为:扁平化已加载的文章数组、用React-Window组件替代原生map渲染、调整无限滚动触发逻辑适配虚拟化场景。

具体实现步骤

  1. 安装react-window
npm install react-window
# 或
yarn add react-window
  1. 修改代码实现虚拟化
    以下是整合后的完整代码,关键修改点已标注:
import { useInfiniteQuery } from '@tanstack/react-query';
import { VariableSizeList as List } from 'react-window'; // 适配不同高度的文章项
import { useRef, useMemo } from 'react';

const API_KEY = '你的API密钥';

async function fetchPage({ pageParam = 1 }) {
    const response = await fetch(
        `https://newsapi.org/v2/everything?q=India&pageSize=30&page=${pageParam}&apiKey=${API_KEY}`
    );
    return response.json();
}

function App() {
    const { isLoading, data, fetchNextPage, isFetchingNextPage, hasNextPage } =
        useInfiniteQuery(["news"], fetchPage, {
            getNextPageParam: (lastPage, allPages) => {
                const nextPage = allPages.length + 1;
                return lastPage.articles.length !== 0 ? nextPage : undefined;
            },
        });

    // 1. 扁平化所有已加载的文章数组
    const allArticles = useMemo(() => {
        return data?.pages?.flatMap(page => page.articles) || [];
    }, [data]);

    // 2. 缓存文章高度,避免重复计算
    const articleHeights = useRef({});

    // 3. 获取单篇文章高度(渲染后缓存实际高度)
    const getArticleHeight = (index) => {
        return articleHeights.current[index] || 300; // 默认高度兜底
    };

    // 4. 渲染单篇文章的回调函数
    const renderArticle = ({ index, style }) => {
        const article = allArticles[index];
        if (!article) return null;

        const handleRef = (el) => {
            if (el && !articleHeights.current[index]) {
                articleHeights.current[index] = el.offsetHeight;
                // 高度更新后重置列表布局,保证滚动准确性
                listRef.current?.resetAfterIndex(index);
            }
        };

        return (
            <div style={style} ref={handleRef} className="news_article">
                <section>
                    <img src={article.urlToImage || 'https://via.placeholder.com/400x200'} alt={article.title} />
                </section>
                <h2>
                    <a href={article.url} target="_blank" rel="noopener noreferrer">{article.title}</a>
                </h2>
                <p>{article.description || '暂无描述'}</p>
                <p>{article.author || '未知作者'}</p>
            </div>
        );
    };

    const listRef = useRef(null);

    // 5. 监听列表渲染事件,触发下一页加载
    const handleItemsRendered = ({ visibleStopIndex }) => {
        // 当滚动到倒数第5条时,自动加载下一页
        if (visibleStopIndex >= allArticles.length - 5 && hasNextPage && !isFetchingNextPage) {
            fetchNextPage();
        }
    };

    if (isLoading) {
        return <p>Loading ...</p>;
    }

    return (
        <>
            <div className="app" style={{ height: '100vh', overflow: 'auto' }}>
                {/* 虚拟化列表核心组件 */}
                <List
                    ref={listRef}
                    height={window.innerHeight}
                    itemCount={allArticles.length}
                    itemSize={getArticleHeight}
                    onItemsRendered={handleItemsRendered}
                    width="100%"
                >
                    {renderArticle}
                </List>
                {/* 底部加载状态提示 */}
                <div className="loader">
                    {isFetchingNextPage ? "Loading..." : hasNextPage ? "" : "No more news"}
                </div>
            </div>
        </>
    );
}

export default App;

关键修改说明

  • 扁平化数组:通过flatMap将多页的articles合并为一个连续数组,满足React-Window对列表数据的格式要求。
  • VariableSizeList:针对新闻文章高度不一致的场景,使用可变高度列表组件,避免固定高度导致的布局错乱;若文章高度固定,可替换为更轻量的FixedSizeList。
  • 高度缓存机制:通过ref缓存每篇文章的实际渲染高度,后续滚动时直接复用,同时在高度更新后重置列表布局,保证滚动位置准确。
  • 滚动触发加载:利用React-Window的onItemsRendered事件,在滚动接近列表末尾时自动触发下一页请求,替代原IntersectionObserver方案,更贴合虚拟化列表的特性。
  • 样式适配:给父容器设置固定高度和滚动属性,确保虚拟化列表能正确计算可视区域范围。

注意事项

  • 给img标签设置默认图片地址和alt属性,避免图片加载失败时的布局异常。
  • 处理description、author等可能为空的字段,避免渲染空内容影响体验。
  • 列表容器必须设置明确高度,否则React-Window无法计算可视区域,虚拟化功能失效。

内容的提问来源于stack exchange,提问作者Moumita Dhar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:35:12