@ code with agents/ openapi- react- query (英语)
📖 完整文档
@codewithagents/openapi-react-query 是一个 OpenAPI React 查询码源工具, 生成已键入的 TanStack 查询 v5 hooks ()useQuery, (中文(简体) ), useMutation,关键工厂)直接取自您 OpenAPI 3.1 spec,没有手写锅炉板,并且完全TypeScript严格规范的遵守.
- 每个操作一个钩子(单位:千美元)
useQuery每个获取和一个useMutation每写一篇。 类型直接取自生成的客户端,不重复. queryOptions()工厂: 一个平原xxxQueryOptions()工厂和工厂useQuery钩,启用queryClient.prefetchQuery()在 Next.js App 路由器服务器组件和<HydrationBoundary>SSR图案.- 智能细节钩: 路径- 参数钩钩在参数时自动禁用
null或者说undefined。无enabled: !!id在每一个呼叫网站. - 包括主要工厂: 每个资源的结构化缓存密钥( q)
all(), (中文(简体) ),list(params), (中文(简体) ),detail(id)) 用于一贯无效. - 变异时自动生效设置
auto_invalidate: true和突变钩使关于成功的相关查询无效,没有useQueryClient需要锅炉板. - 悬浮变种设置
suspense: true生成useSuspense*钩子和每个查询钩一起. - 高级清洁输出: 每一份生成的文件通行证
prettier --check从盒子里出来.
此软件包会消耗已键入的获取客户端 openapi-zod-ts它有运行时间依赖它,所以 openapi-zod-ts 必须先跑。 生成的钩子直接取自该客户端输出的数据和可变类型. 它与工具链中的其他发电机并列, @codewithagents/openapi-server (服务接口加可选) hono | express | fastify | none 路由器)和 @codewithagents/openapi-msw (种子MSW v2 处理器),加 @codewithagents/api-errors 运行时间助手。 见 petstore-快取演示 用来证明这些东西是连在一起的.
安装
npm install -D @codewithagents/openapi-react-query openapi-zod-ts
npm install @tanstack/react-query
@tanstack/react-query ^5 是一个对等的依赖,而 React Query v5 需要 React 18 或 later. openapi-zod-ts 是此生成器的运行时间依赖, 必须先输出其客户端( 见 生成) (中文(简体) ).
配置
见 完整配置参考 所有选项,包括 overrides 编辑 --config CLI旗.
创建 openapi-react-query.config.json 在您的工程根中 :
{
"input_openapi": "./openapi.json",
"output": "./src/api",
"stale_time": 30000,
"gc_time": 300000,
"suspense": false,
"auto_invalidate": false,
"infinite_query": "auto"
}
| 外地 | 需求 | 默认 | 说明 |
|---|---|---|---|
input_openapi | 对 | 无 | OpenAPI 3.x spec( JSON 或 YAML) 光谱 |
output | 对 | 无 | 写入生成文件的目录( 与 Openapi- zod- ts 输出相同) |
stale_time | 没有 | 0 | staleTime 适用于所有 useQuery 钩子 |
gc_time | 没有 | 300000 | gcTime 适用于所有 useQuery 钩子 |
suspense | 没有 | false | 何时 true生成一个 useSuspense* 变量与每个查询钩 |
auto_invalidate | 没有 | false | 何时 true, 变异钩子自动验证成功相关查询 |
infinite_query | 没有 | 'auto' | boolean | 'auto'生成 a useXxxInfinite 标签列表操作的钩子 . 'auto' (一) 检测被粘合物得到; true 力量生成, false 关闭它 |
typed_errors | 没有 | false | 何时 true,会键入其错误作为客户端的过操作错误别名(例如. GetTaskError),这样 error.status 缩进 error.body (见 按状态输入错误(中文(简体) ) |
queries | 没有 | false | 何时 true生成一个 useXxxQueries(paramsList, options?, combine?) 每个带有参数的 GET 操作, 用于动态的并行查询( 见 动态并行查询(中文(简体) ) |
optimistic_updates | 没有 | false | 何时 true,变异钩接受 optimistic: { queryKey, updater } 选项,在请求前更新缓存并返回出错(参见 优化更新(中文(简体) ) |
overrides | 没有 | 无 | 资源缓存时间(见 资源缓存时间(中文(简体) ) |
生成
先运行两个发电机, openapi- zod- ts :
npx openapi-zod-ts
npx openapi-react-query
或加进 package.json数字 :
{
"scripts": {
"generate": "openapi-zod-ts && openapi-react-query"
}
}
试钩? 输出目录也获得生成 test-utils.ts 与 createTestQueryClient() (不复试, gcTime: 0和 createWrapper() 联合国 QueryClientProvider 包装 renderHook),所以你不会手写设置。 见 测试。 。 。 .
产生什么
说到这个 /tasks 资源, hooks.ts 包含:
钥匙厂: 每个资源一个, 用于缓存无效 :
// Key factory (id stays string here; the hook widens it to allow undefined/null)
export const taskKeys = {
all: () => ['tasks'] as const,
list: (params?) => ['tasks', 'list', params] as const,
detail: (id: string) => ['tasks', id] as const,
}
查询选项厂: 每个 GET 操作一次, 用于 RSC / 服务器侧前置 :
// Plain function, no hooks, safe to call in any context (RSC, loaders, tests)
export function listTasksQueryOptions(params?, options?) {
return queryOptions({
queryKey: taskKeys.list(params),
queryFn: () => listTasks(params),
staleTime: 30000,
gcTime: 300000,
...options,
})
}
export function getTaskQueryOptions(id: string, options?) {
return queryOptions({
queryKey: taskKeys.detail(id),
queryFn: () => getTask(id),
staleTime: 30000,
gcTime: 300000,
...options,
})
}
查询钩: 一次操作,消耗上面的工厂:
export function useListTasks(params?, options?) {
return useQuery({
queryKey: taskKeys.list(params),
queryFn: () => listTasks(params),
staleTime: 30000,
gcTime: 300000,
...options, // override any option per call site
})
}
// Detail hook: id widened to allow undefined/null, auto-disabled until id is set
export function useGetTask(
id: string | undefined | null,
options?,
) {
return useQuery({
queryKey: taskKeys.detail(id!),
queryFn: () => getTask(id!),
enabled: id != null && (options?.enabled ?? true),
...options,
})
}
详细钩子( 带有路径参数的钩子) 当参数是 null 或者说 undefined没有了 enabled: !!id 在每一个呼叫网站.
变形钩:每个POST/PUT/PATCH/DELETE:
export function useCreateTask(options?) {
return useMutation({ mutationFn: (vars) => createTask(vars), ...options })
}
export function useUpdateTask(options?) {
return useMutation({ mutationFn: ({ id, body }) => updateTask(id, body), ...options })
}
所有类型来自生成的客户端, 不重复 :
- 数据类型 :
Awaited<ReturnType<typeof listTasks>> - 变量类型 :
Parameters<typeof createTask>[0]
类型 select 变换
每个查询钩, queryOptions 工厂, useSuspense* 钩子和 useXxxInfinite 钩子取出 TData 默认为响应类型。 A 类 select 因此,改变类型 data数字 :
const { data } = useGetTask(id, { select: (task) => task.title })
// ^? string | undefined
const { data: count } = useListTasks({}, { select: (page) => page.items.length })
// ^? number | undefined
// The queryOptions factory carries the select type into useQuery / useQueries
useQuery(getTaskQueryOptions(id, { select: (task) => task.id }))
无 select, (中文(简体) ), data 仍然为原始响应类型,所以现有的呼叫站点(包括明确 UseQueryResult<Task, ApiError> 说明)继续编译.
按状态输入错误
openapi-zod-ts 导出一个操作错误别名 client.ts 对于每个明确记录的操作 4xx 编号 : 5xx 答复,例如:. export type GetTaskError = ApiError<404, NotFound> | ApiError<422, ValidationProblem>与 typed_errors: true 钩子作为错误类型:
const { error } = useGetTask(id)
if (error?.status === 422) {
error.body.errors // ValidationProblem
}
useCreateTask({
onError: (error) => {
if (error.status === 409) toast(error.body.message)
},
})
没有记录错误状态的操作保留 ApiError。这是选入,因为别名只列出 文档 状态:与无证件状态的现有比较error.status === 500) 一旦类型缩小,停止编译,运行时的无证状态仍被抛出为平原 ApiError。 。 。 .
动态并行查询
何时 queries: true 在配置中,每个需要参数的 GET 操作也会得到一个 useXxxQueries 钩: a useQueries 将每个查询与操作的 xxxQueryOptions 工厂,所以缓存键,时间和 queryFn 和单管钩完全吻合.
// One argument (a path param or the params object): pass the values directly
const results = useGetTaskQueries(taskIds) // UseQueryResult<Task, ApiError>[]
// Several arguments: pass one tuple per query, in client-function order
const repos = useGetRepoQueries([
['acme', 'web', { ref: 'main' }],
['acme', 'api', { ref: 'main' }],
])
// Shared options apply to every query; `combine` reduces the results
const titles = useGetTaskQueries(taskIds, { staleTime: 60_000 }, (results) =>
results.flatMap((r) => (r.data ? [r.data.title] : []))
)
没有参数的操作得到否 useXxxQueries 钩子(没有变化). 与 typed_errors: true 结果使用过操作错误的别名.
Next.js App 路由器 SSR 预选
生成的 queryOptions 工厂在React Server组件公司(RSC)工作,没有任何客户范围的工作。 服务器上的预选,然后将脱水缓存传递给客户端:
// app/tasks/page.tsx (React Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { listTasksQueryOptions } from '@/src/api/hooks'
import { TaskList } from './TaskList'
export default async function TasksPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(listTasksQueryOptions())
// or with params: listTasksQueryOptions({ status: 'open' })
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<TaskList />
</HydrationBoundary>
)
}
// app/tasks/TaskList.tsx ('use client')
'use client'
import { useListTasks } from '@/src/api/hooks'
export function TaskList() {
// Cache is already populated from the server; renders without a loading state
const { data } = useListTasks()
return <ul>{data?.map(t => <li key={t.id}>{t.title}</li>)}</ul>
}
细节页中,将 id 传递为普通字符串(工厂不会扩大为 string | undefined | null:
// app/tasks/[id]/page.tsx (React Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { getTaskQueryOptions } from '@/src/api/hooks'
import { TaskDetail } from './TaskDetail'
export default async function TaskPage({ params }: { params: { id: string } }) {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(getTaskQueryOptions(params.id))
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<TaskDetail id={params.id} />
</HydrationBoundary>
)
}
这个 queryOptions 工厂也与 useSuspenseQuery 在呼叫现场:
const { data } = useSuspenseQuery(getTaskQueryOptions(id))
悬浮变种
何时 suspense: true 在配置中,a useSuspense* 钩子与每个查询钩子一起生成 :
// Regular hook (always generated)
const { data, isLoading } = useGetTask(id)
// Suspense variant (generated when suspense: true)
// data is never undefined: wrap parent in <Suspense fallback={...}>
const { data } = useSuspenseGetTask(id)
与反应有关的工作 18 <Suspense> 边框和 Next.js App 路由器装入状态.
突变成功时自动验证
何时 auto_invalidate: true 在配置中,变异钩子在成功时会自动使相关的缓存条目无效。 没有 useQueryClient 在呼叫地点的锅炉板:
// With auto_invalidate: true, invalidation is generated inside the hook
const create = useCreateTask()
create.mutate({ title: 'New task' })
// taskKeys.all() is automatically invalidated on success
const update = useUpdateTask()
update.mutate({ id: '123', body: { title: 'Updated' } })
// taskKeys.all() AND taskKeys.detail('123') are invalidated on success
无效范围 :
POST− 无效resourceKeys.all()PUT编号 :PATCH− 无效resourceKeys.all()+ 键resourceKeys.detail(id)DELETE− 无效resourceKeys.all()
onSuccess 回调( 如果提供在内) options)在无效后调用.
优化更新
何时 optimistic_updates: true 在配置中,每个突变钩都接受一个 optimistic 选项执行 TanStack 查询 v5 乐观更新配方:
const update = useUpdateTask({
optimistic: {
// static key, or derived from the mutation variables
queryKey: (vars) => taskKeys.detail(vars.id),
// annotate `old` to type the cache entry
updater: (old: Task | undefined, vars) => old && { ...old, ...vars.body },
},
})
update.mutate({ id: '123', body: { title: 'Renamed' } }) // the UI updates immediately
生成的钩子所做的:
- 调试数字 :
cancelQueries对于键(因此飞行中获取不能覆盖乐观值),快照getQueryData,则setQueryData还有你的updater。 。 。 . - 错误: 恢复快照(或者如果条目之前没有被缓存的话重新发送).
- 已解决: 使密钥无效, 使服务器状态获胜 .
属于你的 onMutate 编号 : onError 编号 : onSuccess 编号 : onSettled 仍然运行. onMutate 运行在乐观的写法之后,不管它返回到什么位置,都会作为上下文传递给您其他的回调,就像没有选项一样。 与 auto_invalidate: true 资源无效也取决于成功与否。 没有 optimistic 选择钩子的行为与普通突变完全相同。 钩子导出 OptimisticUpdate<TVariables, TQueryData> 财务报告和审定财务报表 OptimisticContext<TContext> 类型.
资源缓存时间
使用 overrides 在配置中设置不同的 staleTime 编号 : gcTime 每个资源 :
{
"input_openapi": "./openapi.json",
"output": "./src/api",
"stale_time": 30000,
"gc_time": 300000,
"overrides": {
"platforms": { "stale_time": 86400000 },
"settings": { "stale_time": 5000, "gc_time": 60000 }
}
}
超接键是API路径中出现的资源名称(例如. platforms 页:1 /api/v1/platforms) (中文(简体) ). 非重叠资源使用全球 stale_time 编号 : gc_time。 。 。 .
用这个
import { useListTasks, useCreateTask } from './src/api/hooks'
function TaskList() {
const { data, isLoading } = useListTasks({ status: 'open' })
// With auto_invalidate: true, useCreateTask invalidates taskKeys.all() automatically
const create = useCreateTask()
if (isLoading) return <Spinner />
return (
<>
{data?.map(task => <Task key={task.id} task={task} />)}
<button onClick={() => create.mutate({ title: 'New task' })}>Add</button>
</>
)
}
多个规格
使用 --config 指向每个供应商不同的配置文件:
npx openapi-zod-ts --config ./config/payments.config.json
npx openapi-react-query --config ./config/payments.config.json
配置文件目录中每个配置解析中的相对路径 .
测试
发电机还产生一个 test-utils.ts 文件并列 hooks.ts出口 createTestQueryClient() 财务报告和审定财务报表 createWrapper() 以去除测试锅炉板。 见 测试你的钩子 用于使用MSW复制可复制的 Vitest 示例.
为了嘲笑HTTP层 这些测试击中, @codewithagents/openapi-msw 生成来自同一 OpenAPI spec 的种子 MSW v2 处理器,因此请求会模拟与客户端同步 hooks call.
解决问题
见 解决问题 用于常见问题的文档中 : 缓存不无效, 细节钩用无效的 ID 点火, 以及 钩子不在 React Server 组件中工作.
许可证
麻省理工学院