TanStack Query for Angular:CreateQueryOptions 查询选项接口深度解析
本文以 docs/framework/angular/reference/interfaces/CreateQueryOptions.md 参考文档为主体,结合 @tanstack/angular-query-experimental 与 @tanstack/query-core 的源码实现,讲清 CreateQueryOptions 这一 Angular Query 核心查询选项接口的类型继承链、泛型参数含义、全部可用配置项及默认值,以及它在 injectQuery、queryOptions 等实际 API 中的消费方式。读完本文,你可以准确理解该接口为何移除 suspense 选项、如何编写类型安全的查询选项,并掌握各配置项的语义与默认行为。
一、CreateQueryOptions 的定位与定义位置
CreateQueryOptions 是 Angular Query(包名 @tanstack/angular-query-experimental)中描述“一个可创建(create)查询全部配置项”的类型接口。参考文档明确给出其定义位置与继承关系:
Defined in: packages/angular-query-experimental/src/types.ts:35 Extends:
OmitKeyof<CreateBaseQueryOptions<TQueryFnData,TError,TData,TQueryFnData,TQueryKey>,"suspense">
在源码中可以逐字印证这一点,types.ts 第 35–43 行即:
export interface CreateQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends OmitKeyof<
CreateBaseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
'suspense'
> {}
注意两个关键实现细节:
- 继承的是
OmitKeyof<CreateBaseQueryOptions<...>, 'suspense'>,即在父类型基础上精确剔除了suspense一个属性。OmitKeyof工具类型定义在 query-core/src/types.ts,与标准Omit不同,它额外允许对“任意字符串键”做安全省略(TStrictly extends 'safely'分支),这是为了兼容泛型展开后键类型不完全确定的场景。 - 继承链上父接口的第 4 个类型参数被固定填充为
TQueryFnData。对照 types.ts 第 21–33 行,CreateBaseQueryOptions有 5 个泛型参数(其中第 4 个是TQueryData),而CreateQueryOptions只有 4 个,因此在向下传递时把TQueryData直接写死为TQueryFnData——这意味着对普通(非无限滚动)查询而言,“缓存中的数据形状”与“queryFn 返回的原始数据形状”是同一个。
为什么移除 suspense?
suspense 是 React 特有的挂起机制。在 query-core 的 QueryObserverOptions 定义 中,其注释为:“If set to true, the query will suspend when status === 'pending' and throw errors when status === 'error'. Defaults to false.”。Angular 的查询通过 injectQuery 返回信号(Signal)化的结果对象来消费状态,并不存在 React 式 Suspense 边界,因此 Angular 包在类型层面直接移除该选项,避免开发者误以为可用。从源码结构看,CreateInfiniteQueryOptions(types.ts 第 75–90 行)对 InfiniteQueryObserverOptions 同样做了 OmitKeyof<..., 'suspense'> 处理,二者保持一致的设计策略。
二、完整类型继承链
CreateQueryOptions 并非孤立接口,其能力来自一条清晰的继承链(每一环均可在仓库源码中查证):
CreateQueryOptions (angular-query-experimental/src/types.ts:35)
└─ OmitKeyof<CreateBaseQueryOptions<...>, 'suspense'>
└─ CreateBaseQueryOptions (angular-query-experimental/src/types.ts:21)
└─ QueryObserverOptions (query-core/src/types.ts:315)
└─ WithRequired<QueryOptions, 'queryKey'>
└─ QueryOptions (query-core/src/types.ts:231)
CreateBaseQueryOptions:定义在 types.ts 第 21–33 行,extends QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,本身不添加成员。参考文档 CreateBaseQueryOptions.md 描述了它与本接口唯一的不同:保留 5 个泛型参数(多出TQueryData)且不剔除suspense。QueryObserverOptions:定义在 query-core/src/types.ts 第 315 行起,在QueryOptions基础上要求queryKey必填(WithRequired<QueryOptions, 'queryKey'>),并补充了enabled、staleTime、refetchInterval等观察层选项。QueryOptions:定义在 query-core/src/types.ts 第 231–281 行,承载retry、gcTime、queryFn、initialData等基础配置。
这条链也解释了参考文档中 Extends 一节为何把父类型写成 OmitKeyof<CreateBaseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>, "suspense"> 的形式——第 4 个实参 TQueryFnData 就是 Angular 层对 TQueryData 的固定绑定。
三、泛型参数逐一解读
参考文档列出了 CreateQueryOptions 的 4 个类型参数,结合 types.ts 第 35–39 行 的默认值与约束:
| 类型参数 | 默认值 | 约束 | 含义 |
|---|---|---|---|
TQueryFnData |
unknown |
无 | 查询函数(queryFn)返回的原始数据类型,也是缓存中存储的数据类型(因 TQueryData 被固定为 TQueryFnData) |
TError |
DefaultError |
无 | 错误类型。DefaultError 来自 query-core(types.ts 第 45–49 行),未通过模块增强声明 Register.defaultError 时即为 Error |
TData |
TQueryFnData |
无 | 最终对外暴露的数据类型。当配置了 select 选择器时,TData 与 TQueryFnData 不同,结果对象上的 data 即为 TData |
TQueryKey |
QueryKey |
extends QueryKey |
查询键类型,默认为 ReadonlyArray<unknown>,允许收窄为具体元组(如 ['post', number])以获得精确推断 |
这些参数并非仅用于展示:它们会一路传递到 QueryObserverOptions,进而约束 queryFn、select、enabled、staleTime 等选项的签名。例如 enabled?: QueryBooleanOption<TQueryFnData, TError, TData, TQueryKey>(query-core/src/types.ts 第 332 行),即允许传一个接收 Query 实例并返回 boolean 的函数。
四、可用配置项全览(继承自 QueryObserverOptions / QueryOptions)
CreateQueryOptions 自身没有任何声明成员(extends ... {} 为空),因此它的“配置项面”完全等于父类型去掉 suspense 后的集合。按来源分两层列出,默认值均以 query-core 源码注释为准:
4.1 观察层选项(来自 QueryObserverOptions)
| 选项 | 类型 | 默认值 | 说明(摘自源码注释) |
|---|---|---|---|
enabled |
boolean | (query) => boolean |
true |
设为 false 可禁用挂载/键变化时的自动拉取(types.ts:332) |
staleTime |
number | 'static' | (query) => ... |
0 |
数据新鲜期(毫秒),设为 Infinity 永不过期(types.ts:339) |
refetchInterval |
number | false | (query) => ... |
false |
设置后按该频率(毫秒)持续后台拉取(types.ts:345) |
refetchIntervalInBackground |
boolean |
false |
标签页/窗口处于后台时是否继续 refetchInterval 拉取(types.ts:355) |
refetchOnWindowFocus |
boolean | 'always' | (query) => ... |
true |
窗口聚焦时若数据过期则重新拉取;'always' 表示总是拉取(types.ts:363) |
refetchOnReconnect |
boolean | 'always' | (query) => ... |
true(networkMode 为 'always' 时除外) |
网络重连时的拉取策略(types.ts:376) |
refetchOnMount |
boolean | 'always' | (query) => ... |
true |
实例挂载时对已有查询的后台拉取策略(types.ts:389) |
retryOnMount |
boolean | (query) => boolean |
true |
挂载时若查询带有错误,是否自动重试(types.ts:400) |
notifyOnChangeProps |
属性名数组 | 'all' | 函数 |
追踪属性访问 | 控制哪些属性变化才触发结果通知(types.ts:408) |
throwOnError |
boolean | (error, query) => boolean |
false |
是否将错误抛出而非放入 error 属性。在 Angular 中该机制由 createBaseQuery 在订阅回调里落地:命中 shouldThrowError(observer.options.throwOnError, ...) 时会 ngZone.onError.emit(state.error) 并 throw(create-base-query.ts 第 129–139 行) |
select |
(data: TQueryData) => TData |
无 | 对缓存数据做变换/取子集,data 的类型随即变为 TData(types.ts:420) |
placeholderData |
值或函数 | 无 | 无 initialData 且处于加载态时占位数据(types.ts:430) |
suspense |
— | — | 本接口已剔除,见第一节 |
4.2 基础层选项(来自 QueryOptions)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
queryKey |
TQueryKey |
必填 | 查询唯一标识,由 WithRequired<QueryOptions, 'queryKey'> 强制(types.ts:322-L325) |
queryFn |
QueryFunction<TQueryFnData> | SkipToken |
无 | 数据获取函数,接收 { client, queryKey, signal, meta, pageParam? } 上下文(QueryFunctionContext 定义) |
retry / retryDelay |
布尔、数字或函数 | 由 QueryClient 默认值决定 |
失败重试策略(types.ts:244-L245) |
networkMode |
'online' | 'always' | 'offlineFirst' |
由客户端默认值决定 | 网络模式,并联动 refetchOnReconnect 的默认值 |
gcTime |
number |
由客户端默认值决定 | 缓存数据成为未使用/非活动后在内存中保留的毫秒数,Infinity 禁用垃圾回收(types.ts:253) |
persister |
QueryPersister |
无 | 包装 queryFn 的持久化钩子(types.ts:255) |
queryHash / queryKeyHashFn |
字符串 / 函数 | 由 key 自动计算 | 自定义缓存哈希(types.ts:256-L258) |
initialData |
值或 () => T | undefined |
无 | 初始数据,可结合 initialDataUpdatedAt(types.ts:259-L260) |
structuralSharing |
boolean | 函数 |
true |
结果间的结构化共享(types.ts:267-L269) |
behavior |
QueryBehavior |
无 | 自定义查询行为 |
meta |
Record<string, unknown> |
无 | 附加元数据载荷(types.ts:276) |
maxPages |
number |
无 | 主要面向无限查询的页数上限,普通查询一般不涉及 |
提示:
QueryClient的defaultQueryOptions会为未显式提供的选项填充默认值。在 Angular 实现中,这一步发生在响应式管道里(见第五节),因此上表“由客户端默认值决定”的项最终取值取决于你在provideAngularQuery中传入的QueryClient配置。
五、CreateQueryOptions 的消费方式与运行时机制
该接口在 Angular 包中主要服务于两处 API:injectQuery 的第三个重载与 queryOptions 系列类型。
5.1 在 injectQuery 中的三个重载
injectQuery 在 inject-query.ts 中提供三个签名(对应参考文档 injectQuery.md 的三份调用签名):
- 传入
DefinedInitialDataOptions(必有initialData)→ 返回DefinedCreateQueryResult; - 传入
UndefinedInitialDataOptions(initialData可为 undefined)→ 返回CreateQueryResult; - 传入
CreateQueryOptions(无initialData约束)→ 返回CreateQueryResult。
后两个选项类型的定义位于 query-options.ts:UndefinedInitialDataOptions 直接 CreateQueryOptions & { initialData?: ... };UnusedSkipTokenOptions 用 OmitKeyof<CreateQueryOptions, 'queryFn'> 排除 SkipToken 后重写 queryFn——可见 CreateQueryOptions 正是这些组合类型的底座。
官方文档给出的基础用法示例(来自 injectQuery.md):
class ServiceOrComponent {
query = injectQuery(() => ({
queryKey: ['repoData'],
queryFn: () =>
this.#http.get<Response>('https://api.github.com/repos/tanstack/query'),
}))
}
响应式用法示例——回调中的信号表达式会被追踪,filter 变化为真值时查询自动启用,回退为假值时禁用:
class ServiceOrComponent {
filter = signal('')
todosQuery = injectQuery(() => ({
queryKey: ['todos', this.filter()],
queryFn: () => fetchTodos(this.filter()),
// Signals can be combined with expressions
enabled: !!this.filter(),
}))
}
5.2 源码中的运行时链路
createBaseQuery(create-base-query.ts)是 injectQuery 与 injectInfiniteQuery 的共同底座,它展示了选项对象如何被消费:
- 用户传入的
optionsFn在computed中执行:queryClient.defaultQueryOptions(optionsFn())(第 58–64 行),因此传入的函数会在 Angular 响应式上下文中运行,其中读取的每个信号都成为依赖,信号变化即触发选项重算; - 默认化后的选项通过
effect中的observer.setOptions(defaultedOptions)交给QueryObserver(第 89–106 行); - 订阅通过
ngZone.runOutsideAngular建立,状态变更再用ngZone.run回注到resultFromSubscriberSignal(第 108–154 行),期间同步维护 Angular 的PENDING_TASKS(fetchStatus === 'fetching'时pendingTasks.add()),使路由导航等机制能正确等待查询。
这段实现同时解释了 4.1 表中 throwOnError 的落地路径,以及为何 suspense 在 Angular 中被移除——错误处理与“等待”分别由错误抛出和 pending tasks 机制承担,而非挂起 UI。
5.3 queryOptions 辅助函数:跨组件复用选项
CreateQueryOptions 也是 queryOptions() 辅助函数三类重载(UndefinedInitialDataOptions / UnusedSkipTokenOptions / DefinedInitialDataOptions)的共同基类型,定义见 query-options.ts 第 76–171 行。该函数运行时只做透传(return options),价值在于类型层面:返回值的 queryKey 会被打上 QueryKeyWithDataTag 数据标签(query-core/src/types.ts 第 82–88 行),从而让 queryClient.getQueryData(queryKey) 获得精确类型。官方指南 query-options.md 给出的典型用法:
import { queryOptions, noop } from '@tanstack/angular-query-experimental'
@Injectable({ providedIn: 'root' })
export class QueriesService {
private http = inject(HttpClient)
post(postId: number) {
return queryOptions({
queryKey: ['post', postId],
queryFn: () => {
return lastValueFrom(
this.http.get<Post>(
`https://jsonplaceholder.typicode.com/posts/${postId}`,
),
)
},
})
}
}
// 组件/服务中使用:
postId = input.required({ transform: numberAttribute })
queries = inject(QueriesService)
postQuery = injectQuery(() => this.queries.post(this.postId()))
queryClient.query(this.queries.post(23)).catch(noop)
queryClient.setQueryData(this.queries.post(42).queryKey, newPost)
select 同样保证类型贯通:
// query.data 的类型是 select 的返回类型,而不是 queryFn 的返回类型
queries = inject(QueriesService)
query = injectQuery(() => ({
...groupOptions(1),
select: (data) => data.title,
}))
六、小结
CreateQueryOptions定义于 packages/angular-query-experimental/src/types.ts 第 35 行,是CreateBaseQueryOptions去除suspense后的收窄版本,4 个泛型参数TQueryFnData、TError、TData、TQueryKey分别约束原始数据、错误、选择后数据与查询键的类型推断。- 它的全部配置能力继承自 query-core 的
QueryObserverOptions→QueryOptions,涵盖enabled、staleTime、refetchInterval、refetchOnWindowFocus、retry、gcTime、select、initialData、throwOnError等选项及其默认值。 - 它是
injectQuery重载与queryOptions辅助函数的类型底座;运行时选项在createBaseQuery的computed响应式管道中经queryClient.defaultQueryOptions默认化后交给QueryObserver,这也是 Angular Query 支持信号驱动、响应式查询选项的根本机制。
如需继续深入,可参阅参考文档目录下的 CreateBaseQueryOptions、CreateInfiniteQueryOptions、InjectQueryOptions 以及 injectQuery 等关联页面。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00