Supabase Studio React Query 数据获取规范:Query Keys、queryOptions 与 Mutation Hook 的源码级实践

📅 发布时间:2026/9/5 21:50:15
Supabase Studio React Query 数据获取规范:Query Keys、queryOptions 与 Mutation Hook 的源码级实践
Supabase Studio React Query 数据获取规范Query Keys、queryOptions 与 Mutation Hook 的源码级实践【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase本篇指南基于 Supabase 仓库中apps/studio/的数据层约定源自.claude/skills/studio-queries/SKILL.md系统讲解 Supabase Studio 控制台前端如何组织 React QueryTanStack Query v5数据获取按领域划分的keys.ts查询键工厂、queryOptions优先的查询定义模式、命令式fetchQuery取数、以及带自动缓存失效与错误兜底的 Mutation Hook 模板。读完你将掌握在apps/studio/data/下为任意新 API 端点编写首个 fetch 或 mutation 的完整套路并能对照真实源码理解每一处约定的工程动机。一、约定的适用范围与参照文件该规范适用于apps/studio/data/下所有查询 Hook、Mutation Hook 与 query key 的新增与评审场景包括「为一个新 API 端点或资源添加第一个 fetch 或 mutation」。规范指定了三个参照实现职责参照文件Query options 模式table-editor-query.tsMutation hook 模板edge-functions-update-mutation.tsQuery keys 工厂keys.ts从源码结构看apps/studio/data/按业务领域edge-functions、projects、database、auth 等拆分为大量同名目录每个目录内聚keys.ts与各自的*-query.ts/*-mutation.ts文件这是该规范能够落地的目录基础。二、Query Keys按领域定义 keys.ts规范的第一条硬性要求每个领域domain定义一个keys.ts导出*Keys辅助函数键用数组 as const声明组件中永远不要内联 query key。规范给出的示例export const edgeFunctionsKeys { list: (projectRef: string | undefined) [projects, projectRef, edge-functions] as const, detail: (projectRef: string | undefined, slug: string | undefined) [projects, projectRef, edge-function, slug, detail] as const, }真实的 keys.ts 与示例完全一致并在此基础上多出了body与lastHourStats两个键export const edgeFunctionsKeys { list: (projectRef: string | undefined) [projects, projectRef, edge-functions] as const, lastHourStats: (projectRef: string | undefined, functionIds: string[] [], useOtel: boolean false) [projects, projectRef, edge-functions, last-hour-stats, normalizeFunctionIds(functionIds), { otel: useOtel }] as const, detail: (projectRef: string | undefined, slug: string | undefined) [projects, projectRef, edge-function, slug, detail] as const, body: (projectRef: string | undefined, slug: string | undefined) [projects, projectRef, edge-function, slug, body] as const, }这组代码展示了三个关键设计点层级前缀所有键都以[projects, projectRef, ...]开头。React Query 的invalidateQueries基于前缀匹配因此 mutation 成功后只需失效xKeys.list(projectRef)即可自动覆盖该列表下所有 detail/body 派生缓存——这是「mutation 里同时失效 list 与 detail 两条键」约定的底层原因。as const固化元组让 query key 的类型成为字面量元组而非宽泛的QueryKey从而在useQuery/fetchQuery调用处获得参数收窄与拼写检查。参数归一化文件顶部的normalizeFunctionIds去重 排序保证「同一组函数 id 不同顺序」产生同一个缓存键避免缓存碎片化。这是对「query key 即缓存身份」这一语义的直接应用。三、Query Options推荐的查询定义模式规范明确优先使用tanstack/react-query的queryOptions因为它既能配合声明式的useQuery()又能配合命令式的queryClient.fetchQuery()且全程保持类型安全。3.1 完整模板规范给出的标准模板原样继承自 SKILL.mdimport { queryOptions } from tanstack/react-query import { xKeys } from ./keys import { get, handleError } from /data/fetchers import { IS_PLATFORM } from /lib/constants import { ResponseError } from /types export type XVariables { projectRef?: string } export type XError ResponseError async function getX({ projectRef }: XVariables, signal?: AbortSignal) { if (!projectRef) throw new Error(projectRef is required) const { data, error } await get(/v1/projects/{ref}/x, { params: { path: { ref: projectRef } }, signal, }) if (error) handleError(error) return data } export type XData AwaitedReturnTypetypeof getX export const xQueryOptions ({ projectRef }: XVariables) queryOptions({ queryKey: xKeys.list(projectRef), queryFn: ({ signal }) getX({ projectRef }, signal), enabled: IS_PLATFORM typeof projectRef ! undefined, })模板配套五条规则导出XVariables、XData、XError三个类型以领域名作前缀。XData通过AwaitedReturnTypetypeof getX推导——由于 HTTP 客户端是全类型生成的返回值类型自动跟随 OpenAPI 类型变化无需手写。实现一个私有的getX(variables, signal?)函数要求缺少必填变量时抛错把signal透传给请求以支持取消失败时调用handleError(error)该函数会抛出见下文 5.2成功时返回data。getX不导出——命令式取数应走queryClient.fetchQuery(xQueryOptions(...))让请求与缓存策略保持一致。用enabled做门控保证必填变量未就绪前查询不会发出。平台专属查询把IS_PLATFORM来自 lib/constants其定义为process.env.NEXT_PUBLIC_IS_PLATFORM true并入enabled本地自托管部署下直接不发起平台 API 请求。不要给xQueryOptions追加额外参数——调用方需要覆盖时通过解构展开实现{ ...xQueryOptions(vars), enabled: true }。这一条约束保证了 options 工厂的签名稳定、可复用。3.2 真实实现佐证table-editor-query.ts 是该模式的落地版本它导出tableEditorQueryOptionsqueryOptions工厂、useTableEditorQuery包装useQuery在enabled上叠加typeof projectRef ! undefined typeof id ! undefined !isNaN(id)校验、以及prefetchTableEditor(client, vars)直接client.fetchQuery(tableEditorQueryOptions(...))即私有getTableEditor被封装在 options 内、对外只暴露 options 的具体体现。该 hook 还展示了按查询特点覆盖缓存策略的写法return useQueryTableEditorData, TableEditorError, TData({ ...tableEditorQueryOptions({ projectRef, connectionString, id, scoped }), enabled: enabled typeof projectRef ! undefined typeof id ! undefined !isNaN(id), refetchOnWindowFocus: false, refetchOnMount: false, staleTime: 5 * 60 * 1000, ...options, })注意...options放在最后这正是「调用方通过解构覆盖默认项」约定的体现——UseCustomQueryOptions类型把queryKey从 options 中剔除见 types/react-query.ts调用方因此无法在覆盖时意外改动查询键只能覆盖enabled、staleTime等行为项。四、在组件中使用 Query Options规范推荐的组件侧用法import { useQuery } from tanstack/react-query import { xQueryOptions } from /data/x/x-query const { data, isPending, isError } useQuery(xQueryOptions({ projectRef: project?.ref }))组件层使用规则同样被明确使用 React Query v5 的 flag 命名isPending表示首次加载尚无数据isFetching表示后台刷新已有数据、正在重取按固定顺序显式渲染三态pending → error → success避免三态渲染互相覆盖导致的状态闪烁。五、命令式取数组件外或回调中在回调、非 React 上下文或「先取数再决定后续动作」的场景规范要求走queryClient.fetchQuery而非直接调 API示例const queryClient useQueryClient() const { data: project } useSelectedProjectQuery() const handleClick useCallback( async (id: number) { const data await queryClient.fetchQuery(xQueryOptions({ id, projectRef: project?.ref })) // use data... }, [project?.ref, queryClient] )这样做的收益是取数结果进入缓存后续useQuery可命中、继承全局重试/staleTime策略、且与声明式查询共享同一个 query key。真实代码中 table-editor-query.ts 的prefetchTableEditor就是同款用法的路由级预取版本。六、Mutation Hook 模板规范对 mutation 的要求导出一个Variables类型包含projectRef、资源标识符如slug与payload实现私有的updateX(vars)内含必填变量校验与handleError包装为useXMutation()接收去掉mutationFn的UseMutationOptions在onSuccess中用await Promise.all([...])同时失效list()与detail()两条键未提供onError时默认toast.error(...)。import { useMutation, UseMutationOptions, useQueryClient } from tanstack/react-query import toast from react-hot-toast import { xKeys } from ./keys type XUpdateVariables { projectRef: string; slug: string; payload: XPayload } export const useXUpdateMutation ({ onSuccess, onError, ...options }: UseMutationOptionsXData, XError, XUpdateVariables {}) { const queryClient useQueryClient() return useMutation({ mutationFn: updateX, async onSuccess(data, variables, context) { await Promise.all([ queryClient.invalidateQueries({ queryKey: xKeys.detail(variables.projectRef, variables.slug), }), queryClient.invalidateQueries({ queryKey: xKeys.list(variables.projectRef) }), ]) await onSuccess?.(data, variables, context) }, async onError(error, variables, context) { if (onError undefined) toast.error(Failed to update: ${error.message}) else onError(error, variables, context) }, ...options, }) }一个实现细节值得注意模板把...options放在配置对象末尾意味着调用方传入的任何字段如onMutate、retry都可以覆盖默认行为而内置的失效逻辑又先于onSuccess?.(...)执行——保证缓存刷新不会因调用方忘记处理而漏掉。6.1 真实 Mutation 实现edge-functions-update-mutation.ts 是该模板的参照实现完整覆盖了规范要点export async function updateEdgeFunction({ projectRef, slug, payload }: EdgeFunctionsUpdateVariables) { if (!projectRef) throw new Error(projectRef is required) const { data, error } await patch(/v1/projects/{ref}/functions/{function_slug}, { params: { path: { ref: projectRef, function_slug: slug } }, body: payload, }) if (error) handleError(error) return data }async onSuccess(data, variables, context) { const { projectRef, slug } variables await Promise.all([ queryClient.invalidateQueries({ queryKey: edgeFunctionsKeys.detail(projectRef, slug) }), queryClient.invalidateQueries({ queryKey: edgeFunctionsKeys.list(projectRef) }), ]) await onSuccess?.(data, variables, context) }, async onError(data, variables, context) { if (onError undefined) { toast.error(Failed to update edge function: ${data.message}) } else { onError(data, variables, context) } }对照模板有两处仓库现状差异阅读代码时需注意其一当前实现从sonner引入toast模板中写作react-hot-toast说明 toast 库已演进落地时以仓库实际依赖为准其二该文件的 options 类型是OmitUseCustomMutationOptionsData, ResponseError, Variables, mutationFn而 types/react-query.ts 中UseCustomMutationOptions已标注deprecated注释建议直接使用UseMutationOptions——模板的新写法直接UseMutationOptions即代表收敛方向。payload类型{ name?, verify_jwt?, import_map? }也与 OpenAPI 的FunctionUpdate端点对应。七、底层支撑fetchers 与全局 QueryClient模板里的get/handleError并非空泛约定它们对应 fetchers.ts 中的真实实现理解这两点能解释为什么模板可以放心地「失败时只写一行if (error) handleError(error)」类型化 HTTP 客户端fetchers.ts用openapi-fetch基于生成的paths类型创建客户端导出GET/POST/PUT/PATCH/DELETE等具名方法文件底部解构为get, post, put, patch, del路径模板/v1/projects/{ref}/functions/{function_slug}中的{ref}占位符由params.path填充参数与返回类型全链路推导。中间件统一注入Authorization、X-Request-Id并在响应侧补充code、requestId、retryAfter取自Retry-After/X-RateLimit-Reset头、requestPathname等字段。handleError是「抛错函数」其签名为(error, options?) never。它按ERROR_PATTERNS错误消息模式表把错误映射到具体错误类无法匹配时落到UnknownAPIResponseError对于没有可展示消息的未知错误会记录 Sentry 并抛出消息被刻意模糊化的通用错误防止不可信的服务端内容直接进入 UI。正因它永远抛出模板中「成功返回data、失败交给handleError」的二分法才成立。全局缓存行为定义在 query-client.ts 的getQueryClient()中这也是模板不需要每个查询重复配置的基础默认值staleTime: 60 * 10001 分钟作为全局默认重试策略4xx除 429 外不重试——429 需按retryAfter退避重试注释解释了原因若对限流立即失败前端会重新发起新请求反而放大限流风暴特定重路径如/platform/pg-meta/:ref/query跳过重试以减负但 429 例外最多重试 3 次MAX_RETRY_FAILURE_COUNT 3retryDelay错误携带retryAfter时按其换算毫秒否则指数退避Math.min(1000 * 2 ** failureCount, 30000)本地开发!IS_PLATFORM时onlineManager.setOnline(true)模拟始终在线避免离线态干扰调试。八、落地清单为一个新端点在apps/studio/data/domain/下新增取数时按以下顺序核对即可覆盖规范全部要点在keys.ts中用as const数组键补充list/detail等工厂函数保持[projects, projectRef, ...]层级前缀查询文件导出XVariables/XData/XError私有getX完成「校验必填变量 → 透传signal→ 失败走handleError」xQueryOptions用queryOptions包装enabled门控必填变量平台专属查询叠加IS_PLATFORM不扩展工厂参数调用方解构覆盖组件内useQuery(xQueryOptions(...))按 pending → error → success 渲染区分isPending与isFetching回调或非 React 场景用queryClient.fetchQuery(xQueryOptions(...))Mutation 导出Variables类型与私有updateXonSuccess中Promise.all失效 list detail 键onError缺省toast.error重试与退避交给全局 QueryClient 默认值仅在查询语义特殊时如 table-editor 的staleTime: 5 分钟、关闭窗口聚焦重取按查询覆盖。整套约定本质上是把「缓存身份、取数逻辑、缓存策略、失效联动」四件事分别锁死在keys.ts、私有 fetch 函数、queryOptions工厂与 mutation 的onSuccess里使apps/studio/data/下数百个领域文件保持同构、可机械评审也为 LLM 或新成员按模板补写新端点代码提供了确定性基础。【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考