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

如何基于useInView控制useInfiniteQuery仅在视图可见时发起首次请求?

解决方案:控制useInfiniteQuery首次请求仅在视图可见时触发

核心思路是利用useInfiniteQuery的enabled配置项,将其与视图可见状态绑定,只有当视图进入视口时,才允许发起首次请求。以下是具体实现方案:

方案一:使用react-intersection-observer的useInView

import { useInfiniteQuery } from '@tanstack/react-query';
import { useInView } from 'react-intersection-observer';

const InfiniteScrollList = () => {
  // 绑定列表容器,获取视图可见状态,仅触发一次可见判定
  const { ref, isInView } = useInView({
    threshold: 0,
    triggerOnce: true
  });

  const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery({
    queryKey: ['infinite-list-data'],
    queryFn: async ({ pageParam = 1 }) => {
      // 替换为你的实际数据请求逻辑
      const response = await fetch(`/api/list?page=${pageParam}`);
      return response.json();
    },
    getNextPageParam: (lastPage) => {
      // 根据接口返回判断是否有下一页
      return lastPage.hasMore ? lastPage.nextPage : undefined;
    },
    // 关键配置:仅当视图可见时启用初始请求
    enabled: isInView
  });

  // 滚动加载逻辑:滚动到底部时触发下一页请求
  const handleScroll = (e) => {
    const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
    if (scrollTop + clientHeight >= scrollHeight - 20 && hasNextPage && !isLoading) {
      fetchNextPage();
    }
  };

  return (
    <div 
      ref={ref} 
      onScroll={handleScroll} 
      style={{ height: '500px', overflowY: 'auto', border: '1px solid #eee' }}
    >
      {data?.pages.map((page, pageIndex) => (
        <div key={pageIndex}>
          {page.items.map(item => (
            <div key={item.id} style={{ padding: '16px', borderBottom: '1px solid #eee' }}>
              {item.content}
            </div>
          ))}
        </div>
      ))}
      {isLoading && <div style={{ padding: '16px', textAlign: 'center' }}>加载中...</div>}
    </div>
  );
};

export default InfiniteScrollList;

方案二:手动实现IntersectionObserver

如果不想依赖第三方库,可以手动写交叉观察者逻辑:

import { useInfiniteQuery } from '@tanstack/react-query';
import { useEffect, useRef, useState } from 'react';

const InfiniteScrollList = () => {
  const [isInView, setIsInView] = useState(false);
  const listRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.unobserve(entry.target); // 仅监听一次可见状态
        }
      },
      { threshold: 0 }
    );

    listRef.current && observer.observe(listRef.current);

    return () => {
      listRef.current && observer.unobserve(listRef.current);
    };
  }, []);

  const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery({
    queryKey: ['infinite-list-data'],
    queryFn: async ({ pageParam = 1 }) => {
      const response = await fetch(`/api/list?page=${pageParam}`);
      return response.json();
    },
    getNextPageParam: (lastPage) => lastPage.hasMore ? lastPage.nextPage : undefined,
    enabled: isInView
  });

  const handleScroll = (e) => {
    const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
    if (scrollTop + clientHeight >= scrollHeight - 20 && hasNextPage && !isLoading) {
      fetchNextPage();
    }
  };

  return (
    <div 
      ref={listRef} 
      onScroll={handleScroll} 
      style={{ height: '500px', overflowY: 'auto', border: '1px solid #eee' }}
    >
      {/* 列表渲染逻辑同方案一 */}
    </div>
  );
};

关键要点说明

  • enabled: isInView:直接控制useInfiniteQuery是否发起初始请求,只有视图进入视口时,该值为true,才会触发首次数据请求。
  • triggerOnce/手动unobserve:确保仅监听一次视图进入事件,避免后续视图进出时反复修改enabled状态,影响请求逻辑。
  • 后续滚动加载的fetchNextPage是手动调用的,不受enabled配置限制,因此滚动加载更多的逻辑可以正常工作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:01:01