十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TanStack Query Angular 无限查询实战:injectInfiniteQuery、maxPages 与双向分页

TanStack Query Angular 无限查询实战:injectInfiniteQuery、maxPages 与双向分页 TanStack Query Angular 无限查询实战injectInfiniteQuery、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在 Angular 项目中实现加载更多Load More或无限滚动Infinite Scroll列表需要同时管理分页参数、逐页追加数据、后台刷新与双向加载等状态。本文基于 Angular 官方指南 Infinite Queries完整讲解tanstack/angular-query-experimental包中injectInfiniteQuery的选项、信号SignalAPI 与模板用法并结合仓库中可运行的示例工程 examples/angular/infinite-query-with-max-pages 与核心源码说明分页参数pageParam、maxPages限制、双向加载等机制的落地细节。读完后你可以直接在自己的 Angular 应用中搭建带游标或页码分片的列表查询并掌握手动增删缓存页的正确姿势。injectInfiniteQuery与普通查询的不同点普通查询通过injectQuery声明而可追加加载的列表查询使用injectInfiniteQuery。两者的差异集中在返回的数据结构和新增的选项/信号上对应 React 版useInfiniteQuery的文档 docs/framework/react/guides/infinite-queries.mdAngular 指南中的代码即由该文档替换而来useQuery → injectQuery、useInfiniteQuery → injectInfiniteQuery返回的data不再是单份数据而是无限查询数据结构InfiniteDatadata.pages已加载页面的数组data.pageParams与页面一一对应的分页参数数组。可用fetchNextPage、fetchPreviousPage方法加载下一页/上一页fetchNextPage为单向场景的必需项。必须提供initialPageParam指定第一页使用的分页参数queryFn的入参会以{ pageParam }的形式收到它。getNextPageParam/getPreviousPageParam选项用于判断是否还有更多数据并计算下一/上一页的参数。hasNextPage信号为true当且仅当getNextPageParam返回的值既不是null也不是undefinedhasPreviousPage同理。isFetchingNextPage与isFetchingPreviousPage信号可用来区分后台刷新与加载更多两种抓取状态。注意initialData、placeholderData选项如有提供必须符合{ pages, pageParams }的数据结构。在 Angular 实现中这些状态不是响应式属性而是信号模板里通过query.isPending()、query.data()、query.hasNextPage()的方式读取组件内则可以用computed组合它们。从源码看inject-infinite-query.ts 内部将InfiniteQueryObserver来自tanstack/query-core交给 createBaseQuery 构建最终结果经由signalProxy包装为信号集合返回见 create-base-query.ts这就是为什么data、error、status都是可调用的形式。基础示例用游标实现 Load More假设后端有一个按游标cursor分页的 API每次返回 3 条projects并附带下次请求用的游标fetch(/api/projects?cursor0) // { data: [...], nextCursor: 3 } fetch(/api/projects?cursor3) // { data: [...], nextCursor: 6 } fetch(/api/projects?cursor9) // { data: [...] } // 没有 nextCursor 即到底构建 Load More 界面的思路是injectInfiniteQuery默认请求第一组数据initialPageParam指定的第一页在getNextPageParam中返回下一组的游标点击按钮时调用fetchNextPage。Angular 版完整组件如下queryFn返回 RxJSObservable时用lastValueFrom桥接为 Promise服务层ProjectsService通过HttpClient请求/api/projects?cursor${page}import { Component, computed, inject } from angular/core import { injectInfiniteQuery } from tanstack/angular-query-experimental import { lastValueFrom } from rxjs import { ProjectsService } from ./projects-service Component({ selector: example, templateUrl: ./example.component.html, }) export class Example { projectsService inject(ProjectsService) query injectInfiniteQuery(() ({ queryKey: [projects], queryFn: async ({ pageParam }) { return lastValueFrom(this.projectsService.getProjects(pageParam)) }, initialPageParam: 0, getPreviousPageParam: (firstPage) firstPage.previousId ?? undefined, getNextPageParam: (lastPage) lastPage.nextId ?? undefined, maxPages: 3, })) nextButtonDisabled computed( () !this.#hasNextPage() || this.#isFetchingNextPage(), ) nextButtonText computed(() this.#isFetchingNextPage() ? Loading more... : this.#hasNextPage() ? Load newer : Nothing more to load, ) #hasNextPage this.query.hasNextPage #isFetchingNextPage this.query.isFetchingNextPage }模板部分使用 Angular 的if/for控制流遍历query.data().pages中的每一页div if (query.isPending()) { pLoading.../p } else if (query.isError()) { spanError: {{ query?.error().message }}/span } else { for (page of query?.data().pages; track $index) { for (project of page.data; track project.id) { p{{ project.name }} {{ project.id }}/p } } div button (click)query.fetchNextPage() [disabled]nextButtonDisabled() {{ nextButtonText() }} /button /div } /div几个值得注意的写法细节选项放在箭头函数() ({...})里返回选项中的表达式包括信号是响应式的getPreviousPageParam/getNextPageParam返回undefined?? undefined即表示没有更多页对应hasPreviousPage/hasNextPage变false按钮文案与禁用状态由computed从isFetchingNextPage、hasNextPage两个信号派生避免在模板中堆砌逻辑。仓库中有一个与上面几乎一致的完整可运行工程 examples/angular/infinite-query-with-max-pages每页 4 条、最多 3 页、支持向前/向后加载其组件实现在 example.component.ts其中maxPages: 3即下文限制页数一节的用法服务层 projects.service.ts 展示了HttpClient与lastValueFrom的配合方式模拟接口 projects-mock.interceptor.ts 则返回nextId/previousId作为游标当nextId为null时触发?? undefined列表即停止。该示例基于 Angular 20 tanstack/angular-query-experimental运行方式为pnpm install后pnpm start见其 README。避免并发抓取fetchNextPage 与 isFetching在列表渲染的同时触发fetchNextPage可能与正在进行的其他抓取产生冲突。无限查询在缓存中只共享一条缓存条目承载所有页面同一时刻只能有一个进行中的 fetch若并发触发两次抓取后完成者可能覆盖前者的数据例如后台刷新被新页数据覆盖。因此推荐在由用户间接触发抓取的场景比如滚动到底部自动加载中先确认查询不在isFetching状态Component({ template: list-component (endReached)fetchNextPage() / , }) export class Example { query injectInfiniteQuery(() ({ queryKey: [projects], queryFn: async ({ pageParam }) { return lastValueFrom(this.projectsService.getProjects(pageParam)) }, })) fetchNextPage() { // 如果已有请求在进行中则不重复触发 if (this.query.isFetching()) return this.query.fetchNextPage() } }如果确实希望允许并发抓取比如让新页请求不打断后台刷新可以在fetchNextPage调用时传入{ cancelRefetch: false }选项默认true即默认会取消正在进行的 refetch。无限查询刷新时的行为当无限查询变为stale需要刷新时各页是按顺序sequentially从第一页开始逐页抓取的。这样即使底层数据发生了变化也不会因为使用了过期的游标而拿到重复数据或漏掉记录。如果该查询的结果从QueryCache中被移除分页状态会重置回初始状态只请求第一页。这一点也直接影响了性能权衡页数越多刷新时的串行请求越多这正是下一节限制页数的动机。双向无限列表getPreviousPageParam 与 fetchPreviousPage如果需要向前也能加载更多例如时间线、评论区向上翻页可以使用getPreviousPageParam选项配合fetchPreviousPage方法与hasPreviousPage、isFetchingPreviousPage信号query injectInfiniteQuery(() ({ queryKey: [projects], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) firstPage.prevCursor, }))其中getNextPageParam/getPreviousPageParam接收两个参数lastPage或firstPage与pages全部已加载页返回值既决定是否还有页非null/undefined即有又作为下一次queryFn的pageParam。仓库示例 example.component.html 中Load Older 按钮绑定query.fetchPreviousPage()禁用与文案逻辑与Load newer完全对称previousButtonDisabled/previousButtonText。用 select 反转页面顺序某些 UI例如新消息在上的列表希望展示顺序与加载顺序相反。此时不必改动抓取逻辑用select选项同时反转pages与pageParams即可query injectInfiniteQuery(() ({ queryKey: [projects], queryFn: fetchProjects, select: (data) ({ pages: [...data.pages].reverse(), pageParams: [...data.pageParams].reverse(), }), }))注意pages和pageParams必须同时反转保持两者的下标对齐关系不变。手动更新无限查询的缓存通过queryClient.setQueryData([projects], updater)手动改缓存时updater 必须维持{ pages, pageParams }结构且两者长度始终一致。常见操作有三种只删掉第一页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), }))务必保持pages与pageParams的数据结构一致否则后续getNextPageParam/getPreviousPageParam拿到错位参数分页状态会悄悄损坏。限制缓存页数maxPages当用户可能加载很多页内存占用增长、或需要刷新一个已含几十页的无限查询所有页会串行重取网络开销大时可以启用受限无限查询用maxPages选项与getNextPageParam/getPreviousPageParam配合在双向加载时按需裁剪缓存中保留的页数。injectInfiniteQuery(() ({ queryKey: [projects], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) firstPage.prevCursor, maxPages: 3, }))上例中缓存的pages数组最多保留 3 页加载第 4 页时第 1 页会被裁掉若需要刷新也只会串行重取这 3 页。指南文档的主示例与仓库示例工程 examples/angular/infinite-query-with-max-pages见 example.component.ts都设置了maxPages: 3可以直接本地运行观察裁剪与刷新行为。API 不返回游标时用 pageParam 自算如果后端不提供游标字段可以用pageParam本身作为游标——因为getNextPageParam与getPreviousPageParam的第三个参数就是当前页的pageParam可以基于它推算下一/上一页参数injectInfiniteQuery(() ({ 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 }, }))注意此处initialPageParam: 0而getPreviousPageParam的边界判断是firstPageParam 1即页码约定从 1 开始计数、初始值 0 仅作为占位起点——按自己的 API 页码约定调整边界即可。返回undefined即停止的约定在所有场景中一致这是hasNextPage/hasPreviousPage信号唯一的判定来源。复用选项infiniteQueryOptions 与可运行示例tanstack/angular-query-experimental还导出 infiniteQueryOptions用于以类型安全的方式把无限查询选项提取为可复用、可共享的对象例如放在 Service 中它会将queryKey打上来自queryFn的数据类型标签使后续setQueryData等操作获得精确类型。指南中的组件式写法则直接使用injectInfiniteQuery(() ({...}))。更多可运行参考无限查询 maxPages本文主示例Angular 20 信号控制流examples/angular/infinite-query-with-max-pages普通分页非无限查询对照examples/angular/pagination依赖 RxJS 的查询写法examples/angular/rxjs框架层其他指南Angular 总览、快速开始、TypeScript。小结能力关键选项 / 信号说明数据形态data.pages/data.pageParams页与参数一一对应手动改缓存时须保持同构起点initialPageParam必填作为首次queryFn的pageParam单向加载getNextPageParamfetchNextPagehasNextPage返回非null/undefined即还有页双向加载getPreviousPageParamfetchPreviousPagehasPreviousPage逻辑与向前方向对称加载状态isFetchingNextPage/isFetchingPreviousPage与后台刷新isFetching区分并发保护isFetching判空或fetchNextPage({ cancelRefetch: false })防止两次抓取互相覆盖顺序展示select反转pagespageParams仅改视图不影响抓取内存/刷新成本maxPages限制缓存保留页数刷新只串行重取该页数无游标 API第三个参数pageParam自算结合空页/边界返回undefinedinjectInfiniteQuery把 React 版useInfiniteQuery的分页状态机完整移植到了 Angular 的 DI 信号体系之上抓取、状态更新经由InfiniteQueryObserver与信号代理create-base-query.ts驱动组件代码只需声明选项、读取信号、调用加载方法。按本文示例起步再对照仓库中的 examples/angular/infinite-query-with-max-pages 工程调参即可覆盖绝大多数列表分页场景。【免费下载链接】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),仅供参考
返回列表