☰
openapi-react-query 的 useInfiniteQuery 实战指南:基于 OpenAPI 的无限分页查询
2026/9/26 2:02:21 网站建设 项目流程
  • 开发工具
  • 代码生成
  • 后端

【免费下载链接】openapi-typescript

Generate TypeScript types from OpenAPI 3 specs

项目地址:https://gitcode.com/gh_mirrors/op/openapi-typescript
点击查看免费下载

导读

useInfiniteQuery是openapi-react-query在@tanstack/react-query原版useInfiniteQuery之上提供的类型安全封装方法,专为"加载更多"式的无限分页场景设计。本文将讲解如何在生成 OpenAPI 类型的基础上,用$api.useInfiniteQuery(...)一键接入游标分页 API,并深入其源码实现,说明分页游标参数是如何自动注入请求的,以及如何通过pageParamName、select等选项定制分页行为。读完本文,你将能在项目里用不到 10 行代码实现一个带"Load More"按钮的完整分页列表。

一、useInfiniteQuery 是什么

openapi-react-query是一个围绕@tanstack/react-query的轻量类型安全封装库,配合openapi-fetch(发起请求)和openapi-typescript(根据 OpenAPI 3 schema 生成类型)使用,让 React 查询代码中的 URL、参数、请求体和响应全部与 schema 严格对齐。

useInfiniteQuery是该库提供的五个核心方法之一(其余为queryOptions、useQuery、useSuspenseQuery、useMutation,见 OpenapiQueryClient 接口定义)。它具备以下特点:

  • 结果与原版一致:返回值完全等同@tanstack/react-query的useInfiniteQuery结果对象,因此data.pages、fetchNextPage、hasNextPage、isFetching等属性都能直接使用;
  • 查询键固定结构:queryKey为[method, path, params];
  • 完全类型化:data和error均由 OpenAPI schema 自动推导,无需手写任何接口类型;
  • 可透传无限查询选项:作为第四个参数传入原版useInfiniteQuery的选项,并额外支持pageParamName自定义游标参数名。

更完整的库背景、特性清单与安装方式见 openapi-react-query 介绍文档。

二、前置准备:安装与类型生成

在使用useInfiniteQuery之前,需要安装本库及两个配套依赖(参见 setup 说明):

npm i openapi-react-query openapi-fetch npm i -D openapi-typescript typescript

然后根据你的 OpenAPI 3 schema 生成 TypeScript 类型:

npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts

官方文档强烈建议在tsconfig.json中开启noUncheckedIndexedAccess,以获得更严格的索引访问类型检查。生成的paths类型将作为后续所有类型推导的根基。

三、完整示例:加载更多分页列表

以下示例来自官方文档,由两个文件组成:src/api.ts负责创建客户端,src/app.tsx使用useInfiniteQuery渲染分页列表。

1. 创建 fetch 客户端与 $api(src/api.ts)

import createFetchClient from "openapi-fetch"; import createClient from "openapi-react-query"; import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript const fetchClient = createFetchClient<paths>({ baseUrl: "https://myapi.dev/v1/", }); export const $api = createClient(fetchClient);

createClient的入参是一个openapi-fetch的FetchClient实例,返回带有queryOptions、useQuery、useSuspenseQuery、useInfiniteQuery、useMutation五个方法的类型安全客户端。关于createFetchClient的更多细节可参考 openapi-fetch 文档。

2. 在组件中使用 useInfiniteQuery(src/app.tsx)

import { $api } from "./api"; const PostList = () => { const { data, fetchNextPage, hasNextPage, isFetching } = $api.useInfiniteQuery( "get", "/posts", { params: { query: { limit: 10, }, }, }, { getNextPageParam: (lastPage) => lastPage.nextPage, initialPageParam: 0, } ); return ( <div> {data?.pages.map((page, i) => ( <div key={i}> {page.items.map((post) => ( <div key={post.id}>{post.title}</div> ))} </div> ))} {hasNextPage && ( <button onClick={() => fetchNextPage()} disabled={isFetching}> {isFetching ? "Loading..." : "Load More"} </button> )} </div> ); }; export const App = () => { return ( <ErrorBoundary fallbackRender={({ error }) => `Error: ${error.message}`}> <MyComponent /> </ErrorBoundary> ); };

要点解读:

  • 第三个参数(请求选项)里的params.query.limit是业务参数,会原样发送;
  • 第四个参数是原版useInfiniteQuery的选项:getNextPageParam从最后一页响应中提取下一页游标(lastPage.nextPage),initialPageParam指定首页游标0;
  • data?.pages按页累积渲染,hasNextPage为false时隐藏按钮,fetchNextPage拉取下一页,isFetching控制按钮禁用与文案。

四、分页参数注入原理:pageParamName 与游标

无限查询与普通查询最大的不同在于:分页游标参数不需要你手动写入请求选项。库会自动把它注入到每次请求的 query 参数中。

从源码实现看(useInfiniteQuery 实现),内部queryFn会做如下合并:

const mergedInit = { ...init, signal, params: { ...(init?.params || {}), query: { ...(init?.params as { query?: DefaultParamsOption })?.query, [pageParamName]: pageParam, }, }, };

也就是说,每次发起请求时:

  1. 保留你传入init中的全部参数(如limit: 10);
  2. 将当前页码pageParam写入params.query[pageParamName];
  3. pageParamName默认为"cursor",因此默认发送的游标参数名是?cursor=xxx;
  4. 首页pageParam取原版选项initialPageParam的值,后续页取getNextPageParam的返回值。

如果你服务的分页参数名不是cursor,可通过infiniteQueryOptions.pageParamName自定义,例如服务端期望follow_cursor:

$api.useInfiniteQuery( "get", "/paginated-data", { params: { query: { limit: 3 } } }, { getNextPageParam: (lastPage) => lastPage.nextPage, initialPageParam: 0, pageParamName: "follow_cursor", // 自定义游标参数名 } );

这一点在官方测试中得到了验证:测试 should use custom cursor params 断言首屏请求携带follow_cursor=0,第二页请求携带follow_cursor=1。

五、API 签名与参数详解

官方文档给出的完整调用形态如下:

const query = $api.useInfiniteQuery( method, path, options, infiniteQueryOptions, queryClient );

参数说明

method(必需)

  • 要使用的 HTTP 方法,如"get"。
  • 该值会作为查询键的一部分。参见@tanstack/react-query官方文档的 Query Keys 一节。

path(必需)

  • 请求的路径名,如"/posts"。
  • 必须是你的 schema 中该 method 下真实存在的路径,否则会得到类型错误。
  • 该值同样作为查询键的一部分。

options

  • 发起请求所用的 fetch 选项(路径/查询参数、请求体等)。
  • 只有当 OpenAPI schema 要求参数时才是必需的;对于无参端点,useInfiniteQuery的init参数仍是必填位(这与useQuery不同,见下文"注意事项")。
  • options.params会作为查询键的一部分,因此不同参数会各自独立缓存。

infiniteQueryOptions

  • pageParamName:用于分页的查询参数名,默认"cursor"。
  • 其余为原版useInfiniteQuery的全部选项(如getNextPageParam、initialPageParam、select、staleTime等),直接透传给@tanstack/react-query。
  • 类型上对应源码中的UseInfiniteQueryMethod定义(类型声明),它在UseInfiniteQueryOptions基础上额外扩展了可选的pageParamName?: string字段。

queryClient(可选)

  • 原版queryClient选项,用于指定使用哪个 QueryClient 实例。

六、源码纵深:useInfiniteQuery 的类型与实现

结合源码可以更清楚地理解它的行为边界。

类型层面:UseInfiniteQueryMethod的返回值类型为:

UseInfiniteQueryResult< InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>, Response["error"] >

其中Response["data"]与Response["error"]由FetchResponse<Paths[Path][Method], Init, Media>推导而来,InfiniteData包装后即为{ pages, pageParams }结构。InferSelectReturnType(源码)会根据select的返回类型动态收敛data的类型——也就是说,如果你用select把InfiniteData变换成了别的形状,data的类型也会随之精确推导。

实现层面:核心queryFn在调用openapi-fetch客户端前完成三件事(源码):

  1. 方法名大写化后从客户端取出对应方法(client["GET"]);
  2. 合并signal(支持请求取消)与init;
  3. 注入pageParam到params.query[pageParamName]。

请求若返回error则直接throw error(而非返回错误对象),这与库内useQuery/useMutation的错误处理策略一致,方便配合 ErrorBoundary 或error状态使用;data则原样返回以累积到pages中。

七、测试验证与进阶用法

仓库中的 useInfiniteQuery 测试套件 覆盖了四条关键行为,可作为使用参考:

  1. 基本分页正确性:首屏请求携带limit=3&cursor=0,调用fetchNextPage()后第二页请求携带cursor=1,data.pages累积两页、hasNextPage为true;
  2. select 变换分页数据:利用select反转pages与pageParams(适合"最新优先"的时间线场景),测试断言反转后pages与pageParams均按预期排序;
  3. 自定义游标参数名:pageParamName: "follow_cursor"时请求参数变为follow_cursor=0/1;
  4. select 返回类型推导:select将InfiniteData拍平为number[]后,result.current.data的类型精确收敛为number[] | undefined,并以expectTypeOf做了编译期断言。

进阶提示

  • 首屏与次页响应结构:通常首屏响应中应包含nextPage(或nextCursor)字段,配合getNextPageParam: (lastPage) => lastPage.nextPage;当返回undefined/null时hasNextPage自动变为false;
  • initialPageParam 必填:原版 TanStack Query v5 要求显式提供initialPageParam,否则首页游标无从谈起;
  • 缓存隔离:由于queryKey含params,不同limit、不同筛选条件的无限查询互不串扰。

八、注意事项与边界

  • init参数位置:与useQuery不同,useInfiniteQuery的init参数在类型签名中是必填位置(init: InitWithUnknowns<Init>),即便端点无参也要传占位值,这是由方法签名(源码)决定的;
  • 分页方式适配:pageParamName注入的是query 参数(URL 查询字符串),如果你的接口采用 offset/limit 数值分页或 Header 分页,需要自行在getNextPageParam中换算成游标,或改用useQuery+ 手动请求;
  • 错误处理:请求错误会以异常形式抛出,建议像示例那样用 ErrorBoundary 包裹,或在组件内捕获;
  • 依赖版本:本库是对@tanstack/react-query的薄封装,其行为随原版版本演进保持一致,请确保项目安装的是与原版接口兼容的版本。

通过以上讲解,你应该已经能够在实际项目中直接使用$api.useInfiniteQuery快速构建类型安全的无限分页列表,并在需要时通过pageParamName与select灵活定制分页语义和数据形态。更多查询相关的封装(如queryOptions、useQuery、useSuspenseQuery)可继续阅读 openapi-react-query 文档目录 下的对应章节。

  • 开发工具
  • 代码生成
  • 后端

【免费下载链接】openapi-typescript

Generate TypeScript types from OpenAPI 3 specs

项目地址:https://gitcode.com/gh_mirrors/op/openapi-typescript
点击查看免费下载

相关推荐

上一篇:RDP Wrapper Library安全部署:如何在企业环境中安全使用并发RDP会话
下一篇:【免费下载】 Serialib:一款简洁高效的跨平台串口通讯库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询