TanStack Query Preact 无限查询(useInfiniteQuery)实战指南:分页加载、无限滚动与 maxPages 内存控制

发布时间:2026/10/31 11:22:58

TanStack Query Preact 无限查询(useInfiniteQuery)实战指南:分页加载、无限滚动与 maxPages 内存控制 TanStack Query Preact 无限查询useInfiniteQuery实战指南分页加载、无限滚动与 maxPages 内存控制【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query无限列表是 Web 前端最常见的交互形态之一用户点击 Load More 按钮在已有数据上增量追加新数据或者在滚动接近底部时自动加载下一页无限滚动。TanStack Query 为这类场景提供了useQuery的增强版本useInfiniteQuery在 Preact 生态中通过tanstack/preact-query包暴露。本文围绕 Preact 框架下的 Infinite Queries 指南 展开该指南是 React 版本指南 的框架衍生文档仅替换了包名与框架字眼结合 preact-query 源码 与 query-core 底层实现完整讲解无限查询的数据结构、分页参数推导、双向翻页、手动更新、页数上限控制等能力帮你写出可复用、无竞态、内存可控的无限列表组件。一、为什么需要useInfiniteQuery与普通查询的本质差异普通useQuery每次只维护一份数据而无限查询需要增量加载多组数据并追加到已有列表中。useInfiniteQuery在 preact-query 的实现 中本质上是把选项交给 query-core 的InfiniteQueryObserver处理export function useInfiniteQuery(options, queryClient?) { return useBaseQuery(options, InfiniteQueryObserver as typeof QueryObserver, queryClient) }也就是说它复用了 useBaseQuery 中关于订阅、乐观更新、Suspense、错误边界的一切机制只是在数据形态与分页能力上做了扩展。当你使用useInfiniteQuery时与普通查询相比会观察到以下不同data不再是你queryFn返回的单页数据而是一个包含无限查询数据的对象data.pages已抓取的各页数据组成的数组data.pageParams抓取每一页时实际使用的 page param 组成的数组与pages一一对应返回值中新增fetchNextPage与fetchPreviousPage两个函数其中fetchNextPage为必用配置项中新增必填的initialPageParam用来指定第一页的初始 page param配置项getNextPageParam与getPreviousPageParam负责两件事判断是否还有更多数据可加载并给出抓取下一页/上一页所需的信息该信息会以额外的pageParam参数传给queryFn返回hasNextPage布尔值当getNextPageParam返回非null/undefined时为true返回hasPreviousPage布尔值当getPreviousPageParam返回非null/undefined时为true返回isFetchingNextPage与isFetchingPreviousPage布尔值用于区分后台刷新状态与正在加载更多状态。注意如果使用了initialData或placeholderData其结构必须与上述无限查询数据一致即包含data.pages与data.pageParams两个属性的对象。在 query-core 的类型定义 中这一结构被抽象为InfiniteDataTData, TPageParam{ pages: ArrayTData, pageParams: ArrayTPageParam }这正是整个无限查询缓存中的单一数据形态。二、第一个例子基于 cursor 的 Load More 列表假设有一个按cursor索引每次返回 3 条projects的 API并同时返回可抓取下一组的 cursorfetch(/api/projects?cursor0) // { data: [...], nextCursor: 3 } fetch(/api/projects?cursor3) // { data: [...], nextCursor: 6 } fetch(/api/projects?cursor6) // { data: [...], nextCursor: 9 } fetch(/api/projects?cursor9) // { data: [...] } // 没有 nextCursor说明已到末尾基于这一信息构造 Load More UI 只需三步默认等待useInfiniteQuery抓取第一组数据在getNextPageParam中返回下一次请求所需的信息下一个 cursor需要加载更多时调用fetchNextPage。下面是在 Preact 中的完整组件实现与 Preact Infinite Queries 指南 中的示例一致包名为tanstack/preact-queryimport { useInfiniteQuery } from tanstack/preact-query function Projects() { const fetchProjects async ({ pageParam }) { const res await fetch(/api/projects?cursor pageParam) return res.json() } const { data, error, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage, status, } useInfiniteQuery({ queryKey: [projects], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) lastPage.nextCursor, }) return status pending ? ( pLoading.../p ) : status error ? ( pError: {error.message}/p ) : ( {data.pages.map((group, i) ( div key{i} {group.data.map((project) ( p key{project.id}{project.name}/p ))} /div ))} div button onClick{() fetchNextPage()} disabled{!hasNextPage || isFetching} {isFetchingNextPage ? Loading more... : hasNextPage ? Load More : Nothing more to load} /button /div div{isFetching !isFetchingNextPage ? Fetching... : null}/div / ) }关键点说明queryFn的入参对象带有pageParam就是由initialPageParam首页或getNextPageParam推导出的 cursordata.pages需要外层循环每一页、内层循环页内条目进行渲染渲染层的 key 建议取在页内条目的唯一 id 上如上例project.iddisabled{!hasNextPage || isFetching}与按钮文案的isFetchingNextPage分支共同保证了在无更多数据或正在请求时不触发重复加载。三、必须避开的竞态无限查询同一时刻只能有一次在途请求必须深刻理解当有请求正在进行时再调用fetchNextPage存在覆盖后台数据刷新结果的隐患。这在一边渲染列表、一边触发 fetchNextPage的场景下尤其关键。原因为一个 Infinite Query同一时刻只能存在一次在途请求。整份分页数据所有页共享同一个缓存条目如果同时发起两次抓取就可能导致数据互相覆盖、丢失某次抓取结果。若你确实需要允许多个请求并发可以在调用fetchNextPage时传入{ cancelRefetch: false }选项默认值为true即默认会取消上一次未完成的抓取。为了让查询过程顺畅无冲突强烈建议在调用加载函数前确认查询不处于isFetching状态——尤其是当调用不是由用户直接触发时例如滚动事件List onEndReached{() hasNextPage !isFetching fetchNextPage()} /在 infiniteQueryBehavior 的实现 中可以看到fetchNextPage/fetchPreviousPage最终都会在 fetch options 的meta.fetchMore.direction上标记方向forward/backwardInfiniteQueryObserver 再据此决定抓取下一页还是上一页。而 createResult 正是通过判断当前状态是否在抓取 抓取方向来推导isFetchingNextPage与isFetchingPreviousPage这两个布尔值的这从源码层面印证了区分加载更多与后台刷新的实现方式。四、无限查询被自动 refetch 时会发生什么当无限查询变为stale并需要重新抓取时每一组页面会被顺序地逐一重新抓取从第一页开始。这么做是有意为之即使底层数据已被修改也不会继续使用陈旧的 cursor 去抓取后续页面从而避免出现重复数据或跳漏记录。从 fetchFn 的实现 可以看到重新抓取时并不直接沿用缓存里旧nextCursor而是每抓完一页都基于最新已抓到的结果通过getNextPageParam(options, result)实时推导下一页的 param直到页数抓完为止。另外如果无限查询的结果被移出了 queryCache例如组件卸载超过gcTime、缓存被清理或手动移除那么分页会从头重新开始只请求初始的那一组数据。对应到代码当oldPages为空时循环次数按旧页数计为 0会退回用oldPageParams[0] ?? options.initialPageParam抓取初始页。五、双向无限列表向前与向后翻页需要上滑翻更早的数据、下滑翻更新的数据的双向列表可借助getPreviousPageParam、fetchPreviousPage、hasPreviousPage、isFetchingPreviousPage这组属性与函数实现useInfiniteQuery({ queryKey: [projects], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) firstPage.prevCursor, })其中getNextPageParam接收(lastPage, pages)作用于最后一页来推导向后的 cursorgetPreviousPageParam接收(firstPage, pages)作用于第一页来推导向前的 cursor。判断是否有上一页的逻辑在 hasPreviousPage 中只有存在getPreviousPageParam且其推导结果不为空时才为true。六、想倒序展示页面用select派生数据如果希望页面以倒序显示例如时间线类型的最新在前可以使用select选项对data做一次派生转换。注意select只是替换了订阅到组件的结果并不会改动缓存useInfiniteQuery({ queryKey: [projects], queryFn: fetchProjects, select: (data) ({ pages: [...data.pages].reverse(), pageParams: [...data.pageParams].reverse(), }), })七、如何手动更新无限查询无限查询在缓存中是一个整体包含全部pages与pageParams的单一数据对象因此使用queryClient.setQueryData手动更新时必须始终保持pages与pageParams的结构一致且两者要同步裁剪否则后续推导 cursor 时会错位。下面给出几种常见操作。手动移除第一页queryClient.setQueryData([projects], (data) ({ pages: data.pages.slice(1), pageParams: data.pageParams.slice(1), }))手动从某一页中移除单条数据const newPagesArray oldPagesArray?.pages.map((page) page.filter((val) val.id ! updatedId), ) ?? [] queryClient.setQueryData([projects], (data) ({ pages: newPagesArray, pageParams: data.pageParams, }))只保留第一页queryClient.setQueryData([projects], (data) ({ pages: data.pages.slice(0, 1), pageParams: data.pageParams.slice(0, 1), }))需要强调的是data.pages中各页的条目一般是一组对象如上面的val.id而上文 cursor 示例中group.data是页内的数据数组具体 filter 的层级取决于你 API 返回的单页结构。八、限制页数maxPages与 Limited Infinite Query某些场景下你可能希望限制缓存中保存的页数以改善性能与体验用户可能加载大量页面时内存占用当无限查询包含几十页而需要重新抓取时网络开销前面提到 refetch 会把所有页顺序重抓一遍。解法是使用Limited Infinite Query通过maxPages选项配合getNextPageParam与getPreviousPageParam在需要时向两个方向抓取页面。下面的示例中查询数据里最多保留 3 页如果发生 refetch也只会顺序重抓这 3 页useInfiniteQuery({ queryKey: [projects], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) firstPage.prevCursor, maxPages: 3, })maxPages在 types.ts 中被注释为无限查询数据中最多存储的页面数。它的实际修剪逻辑在 utils.ts 的 addToEnd / addToStart 中向后加载时新页追加到数组末尾一旦超过max就从头部裁掉最旧的一页newItems.slice(1)向前加载时新页插入到头部超过上限则从尾部裁掉最新的一页newItems.slice(0, -1)。也就是说maxPages在双向无限列表中会淘汰最远离当前视野的页面。九、我的 API 不返回 cursor 怎么办如果后端不返回 cursor可以直接把pageParam本身当作游标使用由于getNextPageParam与getPreviousPageParam的回调同时也能拿到当前页的pageParam以及全部页参数数组你可以基于它做数值运算来推导相邻页。例如按序号翻页的 API可这样实现以当前页为空数组即停止作为终止条件return useInfiniteQuery({ queryKey: [projects], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam) { if (lastPage.length 0) { return undefined } return lastPageParam 1 }, getPreviousPageParam: (firstPage, allPages, firstPageParam) { if (firstPageParam 1) { return undefined } return firstPageParam - 1 }, })这里getNextPageParam返回undefined表示没有下一页getPreviousPageParam在firstPageParam 1时返回undefined表示没有上一页这与前文 hasNextPage / hasPreviousPage 用返回值是否为空判断的逻辑完全对应。十、延伸把无限查询选项抽成共享的infiniteQueryOptions如果你希望同一份无限查询配置既能在组件里用、又能被queryClient.infiniteQuery、queryClient.prefetchInfiniteQuery等命令式 API 复用preact-query 包 提供了与普通 queryOptions 对应的infiniteQueryOptions辅助函数import { infiniteQueryOptions, useInfiniteQuery } from tanstack/preact-query export const projectsOptions infiniteQueryOptions({ queryKey: [projects], queryFn: ({ pageParam }) fetchProjects(pageParam), initialPageParam: 0, getNextPageParam: (lastPage) lastPage.nextId, }) function Projects() { const { data, isPending, isError, error } useInfiniteQuery(projectsOptions) // ... }从源码看infiniteQueryOptions本身只是原样返回选项对象infiniteQueryOptions 实现它的价值在于类型系统让queryKey携带推导出的数据类型从而在组件内与命令式调用之间建立强类型的安全通道。十一、阅读源码的最佳路径想深入理解本文涉及的底层机制建议按以下仓库路径阅读框架层入口useInfiniteQuery.ts重载与注释中附带大量 Preact 可运行示例包括按钮加载更多、IntersectionObserver 无限滚动、skipToken禁用查询等、useBaseQuery.ts订阅与乐观更新、infiniteQueryOptions.ts核心逻辑infiniteQueryBehavior.ts抓取/重抓/方向与maxPages核心、infiniteQueryObserver.tsfetchNextPage/fetchPreviousPage与派生状态、utils.tsaddToEnd/addToStart页数修剪类型契约types.tsInfiniteData接口、maxPages、getNextPageParam等定义相关指南React 版 Infinite Queries 指南本文档的事实源文档、InfiniteQueryObserver 参考文档、以及 Preact 快速开始 与 TypeScript 使用说明。了解 Infinite Query 在 query-core 内部的完整运作方式例如fetchMore方向如何通过meta传递、重抓为何从第一页开始按序执行还能帮助你更准确地预判其在异常与并发场景下的行为。【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/30 1:08:31

如何用 fuels-rs 以自定义共识参数与创世币启动本地 Fuel 测试链

如何用 fuels-rs 以自定义共识参数与创世币启动本地 Fuel 测试链 【免费下载链接】fuels-rs Fuel Network Rust SDK 项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs 当你为合约或交易编写测试时,默认启动的本地节点使用的是默认共识参数和随机生…

2026/10/31 17:49:36

青草莓谈fuzzing: 聊一下目前模糊测试技术的发展的看法

模糊测试技术目前在安全漏洞挖掘中是 非常 火 非常 有 效的一个测试活动,并且也是出论文非常火的一个研究方向黑盒模糊测试技术在研究黑盒模糊测试的时候我们曾提出过很多目前还没有实现的甚至看起来很天真的研究方向通过协议文档自动生成测试模型套件通过协议交互抓…

2026/10/31 17:49:36

涉外接待降本增效:专职翻译与 AI 双屏翻译机的最优组合策略

很多做外贸或拥有常态化涉外业务的企业,管理者们往往面临着一个两难的抉择:是咬牙高薪聘请一位专职翻译,还是尝试引入智能翻译设备?长期聘用专人,虽然沟通有温度、应对复杂局面更灵活,但高昂的固定人力成本…

2026/10/31 17:49:36

TikTok 商品视频没选题?从用户评论中找到下一条内容方向

TikTok 商品视频没选题?从用户评论中找到下一条内容方向 持续运营 TikTok Shop 账号时,最容易耗尽的往往不是商品,而是选题。 团队可能已经做过开箱、演示、口播和使用前后对比,却仍然不知道下一条应该拍什么。继续介绍相同卖点&a…

2026/10/31 17:49:36

游戏开发实战:性能优化分类清单与 Unity / Cocos 引擎技巧

1. 为什么性能优化要"分类"来做 在真实项目里,性能问题从来不是"某个函数慢"这么简单。一个掉帧现象背后,可能是 Draw Call 过多、GC 抖动、物理计算爆炸、纹理内存超限、脚本逻辑空转中的任意一种,甚至是它们叠加的结果…

2026/10/31 17:44:36

推荐一款配音APP之前,我先替你把该踩的坑踩了一遍

做影视解说和小说推文这一年多,配音这件事把我折腾得够呛。一开始图省事用剪映,后来听说讯飞稳、MiniMax情绪好,挨个试了一圈,钱没少花,时间也搭进去不少。到现在手机里还留着好几款,没哪一款能一劳永逸——…

2026/10/30 20:55:10

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/30 20:55:10

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/30 20:55:09

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/31 0:02:41

猫狗目标检测数据集:VOC/COCO/YOLO三格式一体化实战包

简介:本资源是一套面向计算机视觉初学者与YOLO目标检测实践者的猫狗图像数据集及配套开发工具包,专为快速上手目标检测模型训练而设计。资源包含1000张真实场景高清猫狗图片,标注质量高,同时提供VOC(XML)、…

2026/10/31 0:02:41

HDU2017多校标程复现:算法竞赛工程化真题沙盒

简介:本资源为2017年HDU多校联合训练第一场官方配套资料,面向ACM程序设计竞赛备赛学生、算法爱好者及高校编程集训队成员,旨在提供权威解题参考与完整测试验证环境。压缩包共40个文件,含15份C标程(覆盖全部赛题&#x…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑