资讯动态

深入解析 TanStack Query Angular 的 QueriesOptions:递归类型 Reducer 如何推断与约束 injectQueries 的并行查询参数

发布时间:2026/9/8 16:42:50 来源:尧图企业网站定制
深入解析 TanStack Query Angular 的 QueriesOptions递归类型 Reducer 如何推断与约束 injectQueries 的并行查询参数【免费下载链接】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导读QueriesOptions是 Angular Querytanstack/angular-query-experimental中服务于injectQueries的核心类型别名。它以递归 TypeScript 类型 Reducer的形式把调用者传入的 queries 配置数组逐项解包从而推断出每个查询的TQueryFnData、TError、TData等类型参数并在编译期完成约束。读完本文你将透彻理解该类型的三段式结构基础情况、递归步骤、回退分支、深度上限机制、与QueriesResults输入输出对的关系以及它如何支撑 Angular 官方指南中的并行查询与动态查询能力。一、QueriesOptions 是什么、为何需要它在 Angular Query 中injectQueries用于一次性以并行方式执行多条查询其最直观的静态形态与 parallel-queries.md 中描述的一致——多次调用injectQueryexport class AppComponent { usersQuery injectQuery(() ({ queryKey: [users], queryFn: fetchUsers })) teamsQuery injectQuery(() ({ queryKey: [teams], queryFn: fetchTeams })) projectsQuery injectQuery(() ({ queryKey: [projects], queryFn: fetchProjects, })) }但当查询数量在渲染期间是动态变化的例如对数组做.map()生成查询配置时就需要injectQueries。它接受一个返回配置对象的函数其中queries是一组查询配置的数组。问题随之而来这个数组的类型参数是元组tuple级的——每个查询的queryFn返回类型、select的映射类型、throwOnError的错误类型可能各不相同。一个普通的CreateQueryOptions[]根本无法表达第 0 项返回User、第 1 项返回string、第 2 项有额外类型参数这样的精确信息。QueriesOptions正是为解决该问题而生的类型工具。其官方类型别名文档docs/framework/angular/reference/type-aliases/QueriesOptions.md给出的定义注释是QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param即QueriesOptions 这个 Reducer 递归地解包函数参数以推断并强制类型参数。二、类型签名全景文档给出的完整声明如下源实现位于 inject-queries.tstype QueriesOptionsT, TResults, TDepth TDepth[length] extends MAXIMUM_DEPTH ? QueryObserverOptionsForCreateQueries[] : T extends [] ? [] : T extends [infer Head] ? [...TResults, GetCreateQueryOptionsForCreateQueriesHead] : T extends [infer Head, ...(infer Tails)] ? QueriesOptions[...Tails], [...TResults, GetCreateQueryOptionsForCreateQueriesHead], [...TDepth, 1] : ReadonlyArrayunknown extends T ? T : T extends QueryObserverOptionsForCreateQueriesinfer TQueryFnData, infer TError, infer TData, infer TQueryKey[] ? QueryObserverOptionsForCreateQueriesTQueryFnData, TError, TData, TQueryKey[] : QueryObserverOptionsForCreateQueries[];这是一个标准的分治式divide-and-conquer条件类型链每一层都在分发器GetCreateQueryOptionsForCreateQueriesHead中把元组的头元素转换为规范化的查询配置类型再把剩余部分交给下一次递归。它是纯类型层代码运行时不产生任何开销。泛型参数表官方文档原文继承参数约束与默认值职责Textends any[]调用者传入的查询配置元组queries 数组的元素类型序列TResultsextends any[] []递归过程中累积的结果数组累加器初始为空元组TDepthextends ReadonlyArraynumber []递归深度计数器累加器每递归一层追加一个1TResults与TDepth都不作为公开 API 暴露——调用者无需也无法显式传入它们其作用完全是为类型级递归提供局部状态。这正是它被称为Reducer的原因与Array.reduce异曲同工T是待消费的列表TResults是逐步构建的输出TDepth是防止失控的步进计。防御递归失控MAXIMUM_DEPTH定义在 inject-queries.ts 中// Avoid TS depth-limit error in case of large array literal type MAXIMUM_DEPTH 20当递归深度TDepth[length]已达MAXIMUM_DEPTH20时类型立刻泄压退化为宽泛的ArrayQueryObserverOptionsForCreateQueries。这条注释揭示其目的当传入很大的数组字面量时TypeScript 自身的类型实例化深度限制会被触发导致 TS2589 之类编译错误。深度上限让编译器在超出 20 项后不再继续展开元组而是整体收窄为统一的查询配置数组类型——牺牲逐项精确性换取编译稳定性。三个递归分支Reduction 步骤逐一拆解QueriesOptions的条件链深度保护TDepth[length] extends MAXIMUM_DEPTH→ 返回QueryObserverOptionsForCreateQueries[]终止递归。空元组基础情况T extends []→ 返回[]终止递归并清空累加器。这意味着当输入的查询配置数组为空时推导出的结果也为空元组配合QueriesResults会得到空结果数组。单元素基础情况T extends [infer Head]→ 返回[...TResults, GetCreateQueryOptionsForCreateQueriesHead]把已累积的部分结果与转换后的头元素拼合成最终元组。多元素递归步骤T extends [infer Head, ...(infer Tails)]→ 用解构出的头元素转换后追加到TResults同时把TDepth扩为[...TDepth, 1]再对Tails发起递归调用。这是整个类型最核心的一次处理一个元素的归约动作。动态数组回退分支当T无法匹配上述元组形态时处理逻辑进入两条进一步判断: ReadonlyArrayunknown extends T ? T : T extends QueryObserverOptionsForCreateQueries infer TQueryFnData, infer TError, infer TData, infer TQueryKey [] ? Array QueryObserverOptionsForCreateQueries TQueryFnData, TError, TData, TQueryKey : // Fallback ArrayQueryObserverOptionsForCreateQueries若T本质上可由ReadonlyArrayunknown赋值即传入的并不是字面量元组而是一个先声明好的数组变量则直接原样返回T——这是对.map()生成动态查询这类场景的适配此时无法逐项推断类型保持调用者声明的形态否则若T是同构数组即每个元素都是同一种查询配置注释称之为 some known/homogeneous type则从数组元素中推断统一的TQueryFnData/TError/TData/TQueryKey返回带这组类型参数的规范化数组。源注释inject-queries.ts对此有明确说明If T issomearray but we couldnt assign unknown[] to it, then it must hold some known/homogeneous type! use this to infer the param types in the case of Array.map() argument兜底情况Fallback返回ArrayQueryObserverOptionsForCreateQueries保证类型永远可被满足而不会报never。三、逐元素转换器GetCreateQueryOptionsForCreateQueries 的三层分发QueriesOptions只在元组层面做归约真正执行把单个查询配置转换成带精确类型参数的类型的是每轮递归调用的GetCreateQueryOptionsForCreateQueriesHeadinject-queries.ts。它按三种输入形态分发Part 1 —— 显式类型参数对象形态当元素形如{ queryFnData: TQueryFnData; error?: TError; data: TData }时把这三个字段作为显式类型参数填入QueryObserverOptionsForCreateQueries缺少data时退化为两参形态缺少queryFnData时以unknown兜底。Part 2 —— 显式类型参数元组形态当元素是三元组[TQueryFnData, TError, TData]或二元组/单元素元组时按位置提取类型参数。这对应官方类型层面允许调用者以位置参数方式显式声明类型的场景。Part 3 —— 无显式参数时从实现推断当元素是普通配置对象且未声明类型参数时通过queryFn、select、throwOnError三个字段进行类型推断T extends { queryFn?: | QueryFunctioninfer TQueryFnData, infer TQueryKey | SkipTokenForCreateQueries select?: (data: any) infer TData throwOnError?: ThrowOnErrorany, infer TError, any, any } ? QueryObserverOptionsForCreateQueries TQueryFnData, unknown extends TError ? DefaultError : TError, unknown extends TData ? TQueryFnData : TData, TQueryKey : QueryObserverOptionsForCreateQueries这里的细节值得注意只有对象同时按需出现queryFn时才推断出TQueryFnData与TQueryKey当TError推断为unknown时归一化为DefaultError当TData推断为unknown时回落到TQueryFnData——即未写select时TData就等于queryFn的原始返回类型queryFn被允许是SkipTokenForCreateQueriesinject-queries.ts 中把skipToken的类型拓宽为symbol以支持在 skipToken 未保证不可变immutable时依然能正确推断。这意味着配置中有条件跳过查询queryFn: cond ? skipToken : ...也能在QueriesOptions下被正确处理。四、元素级规范化类型QueryObserverOptionsForCreateQueries 与 placeholderData 特例递归分支与分发器最终都收敛到同一目标类型QueryObserverOptionsForCreateQueriesinject-queries.ts// This defines the CreateQueryOptions that are accepted in QueriesOptions GetOptions. // placeholderData function always gets undefined passed type QueryObserverOptionsForCreateQueries TQueryFnData unknown, TError DefaultError, TData TQueryFnData, TQueryKey extends QueryKey QueryKey, OmitKeyof CreateQueryOptionsTQueryFnData, TError, TData, TQueryKey, placeholderData { placeholderData?: TQueryFnData | QueriesPlaceholderDataFunctionTQueryFnData }它基于CreateQueryOptions定义见 types.ts即去除了suspense的CreateBaseQueryOptions构造唯一特例是placeholderData用OmitKeyof移除CreateQueryOptions中原本的placeholderData重新声明为TQueryFnData | QueriesPlaceholderDataFunctionTQueryFnData。注释解释了原因在injectQueries批量场景下placeholderData的函数形态永远只会收到undefined作为参数因为组合结果不是某个单一查询的 previousData因此其签名与单查询场景不同需要单独定义避免类型与运行时语义脱节。五、输入-输出对称QueriesOptions 与 QueriesResults 的配对关系QueriesOptions不是孤立存在的。与之配对的QueriesResults官方文档见 docs/framework/angular/reference/type-aliases/QueriesResults.md定义于 inject-queries.ts用完全相同的递归结构把输入元组映射为结果元组type QueriesResultsT, TResults, TDepth TDepth[length] extends MAXIMUM_DEPTH ? CreateQueryResult[] : T extends [] ? [] : T extends [infer Head] ? [...TResults, GetCreateQueryResultHead] : T extends [infer Head, ...(infer Tails)] ? QueriesResults[...Tails], [...TResults, GetCreateQueryResultHead], [...TDepth, 1] : { [K in keyof T]: GetCreateQueryResultT[K] }QueriesOptions负责输入配置的规范化QueriesResults负责输出结果的推导两者共享同一套归约骨架深度上限、空元组、头尾解构因此输入有多少项、输出就有多少项且位置一一对应。它的最终回退分支用的是映射类型{ [K in keyof T]: GetCreateQueryResultT[K] }对应动态数组场景与QueriesOptions的原样返回数组逻辑互补。结果元素由GetCreateQueryResultHead与GetDefinedOrUndefinedQueryResult产出inject-queries.ts其核心语义是当配置提供了非unknown的initialData直接值或返回确定值的函数时结果类型被提升为DefinedCreateQueryResult使data不再包含undefined否则回落为CreateQueryResult。这一defined 判定逻辑同时在配置侧与结果侧生效保证了injectQueries的类型精度。两个类型最终被组装进InjectQueriesOptionsinject-queries.tsexport interface InjectQueriesOptions T extends Arrayany, TCombinedResult QueriesResultsT, { queries: | readonly [...QueriesOptionsT] | readonly [ ...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueriesT[K] }, ] combine?: (result: QueriesResultsT) TCombinedResult }queries的两种联合分支分别覆盖元素由统一类型参数约束与逐元素分发两种写法combine允许把整组结果二次变换为单一组合值例如TCombinedResult可以是一个聚合对象。六、类型参数如何在运行时落地injectQueries 的调用链类型层之外inject-queries.ts 中injectQueries的运行时实现以信号Signal体系为骨架恰好印证了QueriesOptions中的各类设计optionsFn()被包裹进computed()使信号可以插入到配置中、整个配置保持响应式函数包裹保证了信号变化后嵌入表达式能持续生效见 inject-queries.ts 注释每个查询配置先经queryClient.defaultQueryOptions()补全默认项再据isRestoring()设置_optimisticResultsisRestoring或optimistic使结果在订阅前就处于拉取状态惰性实例化QueriesObserver来自tanstack/query-core通过getOptimisticResult获得乐观结果通过effect()把更新后的配置同步给 observer并用ngZone.runOutsideAngular包裹订阅、以notifyManager.batchCalls批处理状态更新最终把结果信号中的每个查询结果通过signalProxy包装对应 signal-proxy.ts暴露为data()、isPending()这样的信号式访问器若提供了combine则返回组合结果而非结果数组。可以看到QueriesOptions/QueriesResults的类型推导与injectQueries的运行时形态严格对齐类型层保证每个位置能取到什么数据运行时层保证每个位置的信号更新与订阅生命周期正确。类型层中的深度上限、数组回退分支也正好匹配运行时对queries.map(...)动态数组的处理路径。七、实战验证测试用例如何印证类型推导QueriesOptions的类型行为由类型级测试文件 inject-queries.test-d.ts 固化可复现三类典型场景initialData 使 data 变为 defined传入initialData字面量值的查询其结果data推导为不带undefined的类型而显式把initialData写成可能返回undefined的函数时data的类型联合中恢复undefined成员。这正是GetDefinedOrUndefinedQueryResult的分支逻辑inject-queries.tsselect 改变 TDataqueryFn返回Promisenumber且配置select: (data) data 1的查询data推导为boolean | undefined验证了 Part 3 中从select推断TData、并以queryFn返回类型兜底的路径动态混合元组通过[...queries1List, { ...Queries2.get() }]拼接同构数组与单个配置其返回信号被精确推导为Signal[...ArrayCreateQueryResultnumber, Error, CreateQueryResultboolean, Error]直接验证了QueriesOptions头尾递归 数组回退分支的组合行为。这些用例同时展示了skipToken、queryOptions()配置透传、以及自定义可注入封装用CreateQueryOptions泛型复用查询配置等与QueriesOptions配套的进阶用法。八、在 Angular 应用中使用 injectQueries 的实战形态理解类型后落地到 parallel-queries.md 中动态并行的官方形态如下注意该指南注明 injectQueries 仍处于开发完善阶段以下代码用于表达用法export class AppComponent { users signalArrayUser([]) // 动态地生成任意数量的并行查询 userQueries injectQueries(() ({ queries: users().map((user) { return { queryKey: [user, user.id], queryFn: () fetchUserById(user.id), } }), })) }配合combine把多个结果聚合为单一对象可直接在模板中响应式消费// 组合所有用户的加载状态便于统一渲染骨架屏 const queries injectQueries(() ({ queries: ids.map((id) queryOptions({ queryKey: [user, id], queryFn: () fetchUserById(id), })), combine: (results) ({ data: results.map((r) r.data()).filter(Boolean), isPending: results.some((r) r.isPending()), }), }))在依赖查询场景dependent-queries.md中通常优先使用多次injectQuery配合enabled来显式表达先后依赖当查询集合本身动态变化时injectQueriesQueriesOptions提供的元组级类型推导尤其动态场景退化为同构类型或保留调用方声明类型则能避免手写大量CreateQueryResult联合类型。九、总结QueriesOptions是一个纯类型层面的递归 ReducerT是被归约的配置元组TResults是累积输出TDepth是深度守卫通过MAXIMUM_DEPTH 20的深度上限避免 TS 类型实例化爆炸通过空元组/单元素/头尾解构三段式递归逐项规范化对动态数组场景回退为原类型、同构数组场景回退为统一的参数化数组每个元素最终经QueryObserverOptionsForCreateQueries规范化其中placeholderData函数形态被特化为永远收到undefined它与输出侧QueriesResults共享同一递归骨架配合GetCreateQueryResult/GetDefinedOrUndefinedQueryResult实现initialData驱动的 defined 结果提升其行为被类型级测试用例与injectQueries的运行时信号实现双向印证是理解 Angular Query 并行查询类型体系的最佳切入点。参考源码与文档索引官方类型文档QueriesOptions.md、QueriesResults.md类型定义与运行时实现inject-queries.ts支撑类型types.ts、signal-proxy.ts类型级测试inject-queries.test-d.ts使用指南parallel-queries.md、dependent-queries.md【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价