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

使用react-intersection-observer实现无限滚动时fetchData重复调用问题求助

解决react-intersection-observer无限滚动重复调用fetchData的问题

问题描述

我正在开发一个无限滚动组件,使用react-intersection-observer的hook时遇到问题:当inView状态为true时,fetchData函数会被调用两次。问题流程如下:

  • 页面加载时,inView为false。
  • 用户滚动后,inView变为true。
  • 异步调用fetchData,useEffect执行结束。
  • 接口返回结果后,setQueryData更新状态。
  • 状态更新触发useEffect再次执行,但此时新数据还没渲染,关联inView的组件仍在可视范围内。
  • 因为inView仍为true,fetchData被再次调用。

我想过在useEffect执行后立刻把inView设为false,但不确定是否可行,求建议。

现有代码

type LoadMoreState = {
  data: QueryDatabaseResponse[];
  nextCursor: string | undefined;
};

export function LoadMore({ cursor }: { cursor: string }) {
  const { ref, inView } = useInView();
  const [queryData, setQueryData] = useState<LoadMoreState>({
    data: [],
    nextCursor: cursor,
  });

  useEffect(() => {
    const fetchData = async () => {
      if (inView) {
        try {
          const res = await getGoodFirstIssues({
            page_size: 10,
            start_cursor: queryData.nextCursor,
          });
          setQueryData((prevData) => ({
            data: prevData.data.concat(res),
            nextCursor: res.next_cursor || undefined,
          }));
        } catch (error) {
          console.log("There was an error fetching the data", error);
        }
      }
    };
    fetchData();
  }, [inView, queryData]);

解决方案

核心问题分析

你的useEffect依赖了整个queryData对象,每次setQueryData更新状态后,useEffect会重新执行;同时没有添加请求锁机制,导致inView为true时重复发起请求。

具体修复方案

1. 添加请求锁状态

新增isFetching状态标记当前是否正在请求,避免同一条件下重复调用接口:

type LoadMoreState = {
  data: QueryDatabaseResponse[];
  nextCursor: string | undefined;
};

export function LoadMore({ cursor }: { cursor: string }) {
  const { ref, inView } = useInView();
  const [queryData, setQueryData] = useState<LoadMoreState>({
    data: [],
    nextCursor: cursor,
  });
  // 新增请求锁状态
  const [isFetching, setIsFetching] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      // 只有在可视、未在请求、且还有下一页数据时才执行
      if (inView && !isFetching && queryData.nextCursor) {
        setIsFetching(true);
        try {
          const res = await getGoodFirstIssues({
            page_size: 10,
            start_cursor: queryData.nextCursor,
          });
          setQueryData((prevData) => ({
            data: prevData.data.concat(res),
            nextCursor: res.next_cursor || undefined,
          }));
        } catch (error) {
          console.log("There was an error fetching the data", error);
        } finally {
          // 请求结束后释放锁
          setIsFetching(false);
        }
      }
    };
    fetchData();
  }, [inView, queryData, isFetching]);

  return (
    <div ref={ref}>
      {/* 可选:添加加载提示 */}
      {isFetching ? <div>加载中...</div> : null}
    </div>
  );
}

2. 优化useEffect依赖(可选)

将queryData.nextCursor单独提取作为依赖,减少因queryData.data变化触发的不必要执行:

export function LoadMore({ cursor }: { cursor: string }) {
  // ... 其他状态保持不变
  const nextCursor = queryData.nextCursor;

  useEffect(() => {
    const fetchData = async () => {
      if (inView && !isFetching && nextCursor) {
        setIsFetching(true);
        try {
          const res = await getGoodFirstIssues({
            page_size: 10,
            start_cursor: nextCursor,
          });
          setQueryData((prevData) => ({
            data: prevData.data.concat(res),
            nextCursor: res.next_cursor || undefined,
          }));
        } catch (error) {
          console.log("There was an error fetching the data", error);
        } finally {
          setIsFetching(false);
        }
      }
    };
    fetchData();
  }, [inView, nextCursor, isFetching]);
}

3. 调整useInView配置(可选)

通过配置threshold确保组件完全进入视口才触发,减少误触发概率:

const { ref, inView } = useInView({
  threshold: 1.0, // 组件100%进入视口才触发
});

注意:不要直接修改inView状态

inView是react-intersection-observer内部维护的状态,无法手动修改,强行干预会破坏hook的正常逻辑,添加请求锁是更合理的解决方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 03:15:55