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

React中优化无限滚动:控制RTK Query Lazy API调用

基于RTK Query Lazy Query的可靠无限滚动优化实现

你的代码主要存在几个问题导致上下滚动都触发请求、调用时机失控:

  • 滚动事件没有区分向下滚动的行为,只要滚动到阈值不管方向都触发页码递增
  • handleScroll 存在闭包问题,依赖的 imageLoading 始终是初始值,导致判断失效
  • 直接在 pageNumber 的 useEffect 中合并数据,忽略了 getImages 的异步特性,可能拿到旧数据
  • 没有处理「无更多数据」的边界情况,会无限触发无效请求

优化后的完整代码

import { useState, useEffect, useRef } from 'react';
import { useLazyGetImagesQuery } from './services/api'; // 替换为你的API路径

const InfiniteScrollImages = () => {
  const [getImages, { data: currentPageData, isFetching, isSuccess }] = useLazyGetImagesQuery();
  const [allImages, setAllImages] = useState([]);
  const [pageNumber, setPageNumber] = useState(1);
  const [hasMore, setHasMore] = useState(true);
  const lastScrollTop = useRef(0); // 记录上次滚动位置,判断滚动方向

  // 初始化加载第一页
  useEffect(() => {
    getImages(pageNumber);
  }, []);

  // 当新页数据返回时,合并到总数据
  useEffect(() => {
    if (isSuccess && currentPageData?.length) {
      setAllImages(prev => [...prev, ...currentPageData]);
      // 判断是否还有更多数据:如果返回的数据长度小于每页数量,说明没有更多了
      setHasMore(currentPageData.length === 10); // 替换为你的每页数据量
    } else if (isSuccess && !currentPageData?.length) {
      setHasMore(false);
    }
  }, [isSuccess, currentPageData]);

  const handleScroll = () => {
    const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
    // 仅在向下滚动时触发判断
    const isScrollingDown = scrollTop > lastScrollTop.current;
    // 触发请求的条件:向下滚动+到达阈值+不在请求中+还有更多数据
    const shouldFetch = 
      isScrollingDown &&
      scrollTop + clientHeight >= scrollHeight * 0.8 &&
      !isFetching &&
      hasMore;

    if (shouldFetch) {
      const nextPage = pageNumber + 1;
      setPageNumber(nextPage);
      getImages(nextPage);
    }

    // 更新上次滚动位置
    lastScrollTop.current = scrollTop;
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [isFetching, hasMore]); // 依赖最新状态,避免闭包问题

  return (
    <div>
      {allImages.map(image => (
        <div key={image.id} className="image-item">
          <img src={image.url} alt={image.alt} />
        </div>
      ))}
      {isFetching && <div>加载中...</div>}
      {!hasMore && !isFetching && <div>没有更多内容了</div>}
    </div>
  );
};

export default InfiniteScrollImages;

关键优化说明

  • 滚动方向控制:通过 useRef 记录上次滚动位置,仅在向下滚动到阈值时触发请求,彻底解决上下滚动都调用API的问题
  • 异步数据安全合并:监听RTK Query的 isSuccess 和 currentPageData 状态,确保拿到最新的请求结果后再合并数据,避免旧数据污染
  • 防重复/无效请求:
    • 用 isFetching 判断是否正在请求,避免同时发起多个并行请求
    • 用 hasMore 标记是否还有更多数据,无数据时自动停止触发
  • 闭包问题修复:将 isFetching 和 hasMore 加入滚动事件的依赖数组,确保回调能拿到最新状态
  • 请求时机优化:直接在滚动回调中调用 getImages,减少状态更新的异步延迟

额外建议

如果你的业务场景允许,推荐使用RTK Query专门为无限滚动设计的 useInfiniteQuery,它自带分页状态管理、数据合并等能力,能进一步简化代码。

内容的提问来源于stack exchange,提问作者Khairul Hasan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 03:10:31