如何基于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
相关产品推荐
相关产品推荐

