如何用react-query实现无限滚动?解决Hook嵌套及渲染疑问
React Query 实现无限滚动的优化方案及疑问解答
问题描述
场景
项目需要实现无限滚动,当前使用原生JavaScript Fetch API,希望替换为React Query。
问题
React Query以Hook形式存在,无法嵌套在useCallback中,当前手动管理分页数据的写法无法直接适配。
当前实现代码
function RQInfiniteScrollTable({src}:{src:string}){ const [page,setPage] = useState(1); const [pageData,setPageData]:[pageData:Array<object>,setPageData:any] = useState([]); const loadMore = useCallback(async () => { const new_page = page + 1; setPage(new_page); const buffer: Array<object> = [...pageData]; let moreData:Response|null = await fetch([src,new_page].join("/").trim()); let moreData_json:object|null = await moreData.json(); if(moreData_json === null){return;} buffer.push(moreData_json); setPageData(buffer); return ()=>{moreData = null; moreData_json = null;}; },[pageData]); const products:Array<React.ReactNode> = []; // @ts-ignore pageData.forEach(({title,image,price,category,description},index):void=>{ products.push( <div key={index} className={"infinite-item-card"}> <div className={"w-full h-64 relative"}> <Image src={image} alt={title} layout={"fill"} objectPosition={"cover"} objectFit={"cover"}/> </div> <h1>{title}</h1> <p>{price}</p> <p>{category}</p> <p>{description}</p> </div> ); }); return( <div className={"w-full grid justify-items-center"}> <h1 className={"text-2xl w-fit my-8"}>{"Infinite Scroller"}</h1> <div className={"grid"}>{products}</div> <InfiniteScroller loadMore={loadMore}/> {/* 当元素交叉时调用loadMore */} </div> ); }
疑问
- 如何优化适配React Query实现无限滚动?
- useQuery的查询结果是否会触发组件重渲染?
解答
1. 用React Query的useInfiniteQuery实现无限滚动
React Query专门提供了useInfiniteQuery钩子处理无限滚动/分页场景,无需手动管理page、pageData状态,还自带缓存、重复请求拦截、状态管理等能力。重构后的代码如下:
import { useInfiniteQuery } from '@tanstack/react-query'; function RQInfiniteScrollTable({ src }: { src: string }) { // 使用useInfiniteQuery获取分页数据 const { data, fetchNextPage, hasNextPage, isFetchingNextPage, error } = useInfiniteQuery({ queryKey: ['products', src], // 缓存键,确保不同src对应不同缓存 queryFn: async ({ pageParam = 1 }) => { // pageParam是当前要请求的页码,默认从1开始 const response = await fetch(`${src}/${pageParam}`.trim()); if (!response.ok) throw new Error('请求失败'); return response.json(); }, // 告诉React Query如何判断下一页的页码 getNextPageParam: (lastPage, allPages) => { // 根据实际接口返回逻辑调整,示例:若最后一页有数据则返回下一页页码,否则返回undefined return lastPage.length > 0 ? allPages.length + 1 : undefined; } }); // 处理加载错误 if (error) return <div className="text-red-500">加载失败:{(error as Error).message}</div>; // 拼接所有分页数据 const allProducts = data?.pages.flat() || []; // 渲染产品列表 const products = allProducts.map((product, index) => ( <div key={`${product.id}-${index}`} className="infinite-item-card"> {/* 用id+index做key更稳定 */} <div className="w-full h-64 relative"> <Image src={product.image} alt={product.title} layout="fill" objectPosition="cover" objectFit="cover" /> </div> <h1>{product.title}</h1> <p>{product.price}</p> <p>{product.category}</p> <p>{product.description}</p> </div> )); // 定义loadMore:调用fetchNextPage,同时避免重复请求 const loadMore = () => { if (hasNextPage && !isFetchingNextPage) { fetchNextPage(); } }; return ( <div className="w-full grid justify-items-center"> <h1 className="text-2xl w-fit my-8">Infinite Scroller</h1> <div className="grid">{products}</div> {/* 只有还有下一页时才渲染加载触发组件 */} {hasNextPage && <InfiniteScroller loadMore={loadMore} />} {/* 可选:显示加载状态 */} {isFetchingNextPage && <div className="my-4">加载中...</div>} </div> ); }
关键优化点:
- 用
useInfiniteQuery替代手动状态管理,自动处理分页数据拼接与状态控制 - 通过
queryKey实现缓存复用,相同请求直接读取缓存,减少无效请求 getNextPageParam自动判断是否存在下一页,无需手动维护page变量fetchNextPage是React Query提供的稳定方法,自带重复请求拦截,解决了Hook无法嵌套useCallback的问题
2. useQuery的查询结果会触发组件重渲染吗?
会触发,但React Query做了智能重渲染优化,仅在以下场景触发:
- 查询数据发生实际变化(比如新分页数据返回)
- 查询状态改变(如从
idle变为loading、出现error) - 缓存数据被手动更新(如调用
invalidateQueries刷新)
React Query内部通过优化状态引用逻辑,避免无意义重渲染:如果请求返回数据与缓存完全一致,组件不会更新;同时返回的data、isLoading等状态都是稳定引用,不会因组件重渲染导致无效更新。
内容的提问来源于stack exchange,提问作者レノキ乇1ᑌᑭ
相关产品推荐
相关产品推荐

