首页
/ TanStack Query Angular:深入解析 UndefinedInitialDataOptions 类型与可选 initialData 的类型安全设计

TanStack Query Angular:深入解析 UndefinedInitialDataOptions 类型与可选 initialData 的类型安全设计

2026-09-07 09:58:46作者:江焘钦

UndefinedInitialDataOptions 是 TanStack Query 在 Angular(@tanstack/angular-query-experimental)中为普通查询(injectQuery)定义的核心 options 类型别名,专门描述"initialData 可能缺失"这一最常见场景下的查询配置形态。理解该类型,你就掌握了 TypeScript 是如何把"有没有提供初始数据"自动映射为"查询结果 data 是否可能为 undefined"这一整套类型安全机制,从而写出编译期即可捕获空值风险、无需手动断言的可复用查询选项。

本文将从 type-aliases/UndefinedInitialDataOptions.md 的类型声明出发,逐层拆解其定义、底层工具类型与类型参数默认值,并结合 query-options.tsinject-query.ts 等仓库源码与类型级测试,说明该类型别名在实际调用链中如何生效。

一、类型别名总览:定义与出处

仓库 API 参考文档给出的完整定义如下:

type UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> = CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> & object;

它定义于 query-options.ts:13。查看源码可以确认,实际声明比文档页展示的结构更精确,其中可选的 initialData 属性在显式收窄后被展开为三态联合:

export type UndefinedInitialDataOptions<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
> = CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
  initialData?:
    | undefined
    | InitialDataFunction<NonUndefinedGuard<TQueryFnData>>
    | NonUndefinedGuard<TQueryFnData>
}

从语义上理解,这个别名表达的是:当调用方没有提供 initialData(或提供的 initialData 允许为 undefined)时,查询选项仍然合法,但同时意味着查询结果的 data 取值中会包含 undefined

二、Type Declaration 逐项拆解:optional initialData

类型声明中最关键、也是该类型区别于其他 options 类型的字段是 initialData?

optional initialData:
  | InitialDataFunction<NonUndefinedGuard<TQueryFnData>>
  | NonUndefinedGuard<TQueryFnData>;

它可选,共接受三种形态的值:

取值形态 含义 典型写法
不传 / 显式传 undefined 无初始数据,查询首次执行时进入 pending 状态 initialData: undefined
惰性函数(工厂) 返回undefined 初始数据的函数,适合需要计算成本或依赖当前上下文(如时间戳)的场景 initialData: () => getCachedSnapshot()
字面量/同步值 直接给出的、经 NonUndefinedGuard 剔除 undefined 后的初始数据 initialData: { id: 1 }

注意类型声明里有两个约束值得强调:

  1. initialData 本身可以省略,也可以显式赋值为 undefined。这是与 DefinedInitialDataOptions 最本质的区别——后者中 initialData必填的(见 DefinedInitialDataOptions.mdquery-options.ts:40)。
  2. 一旦传入(函数或字面量),其值类型必须是非 undefinedTQueryFnData。也就是说,这个类型不允许你传入"可能返回 undefined 的初始化函数"来假装数据已就绪——如果你确实需要这种"可能没有数据"的函数,则结果会被归入未定义分支。

NonUndefinedGuard:从类型层面剔除 undefined

NonUndefinedGuard 定义于 query-core 的 types.ts:12,实现极为精简:

export type NonUndefinedGuard<T> = T extends undefined ? never : T

T extends undefined ? never : T 是一个条件类型:若 Tundefined(或可被 undefined 实例化,比如 string | undefined 这类联合类型),则映射为 never;否则保留原类型。它的作用是用类型系统把 undefinedinitialData 的合法取值中排除,让"提供初始数据"这一分支在类型层必然拥有明确数据。

InitialDataFunction:惰性初始数据的函数签名

InitialDataFunction 同样定义于 types.ts:173

export type InitialDataFunction<T> = () => T | undefined

值得留意的是:在 query-core 的通用定义中,InitialDataFunction<T> 返回 T | undefined(即允许返回空);而 UndefinedInitialDataOptions 在组合它时把它套在 NonUndefinedGuard<TQueryFnData> 外层,等价于 () => NonUndefinedGuard<TQueryFnData> | undefined。结合前文,undefined 对应"初始化失败/无数据"的分支仍被允许,但此时查询整体被视为"未定义初始数据"的形态。选择函数形态的额外好处是可以配合 initialDataUpdatedAt 使用,让 TanStack Query 基于函数提供的"数据更新时间"做 staleTime 与缓存新鲜度判断。

三、类型参数:默认值与被继承的泛型骨架

UndefinedInitialDataOptions 声明了四个类型参数,继承自 CreateQueryOptions(定义于 types.ts:35):

类型参数 约束/默认值 含义
TQueryFnData 默认 unknown queryFn 返回的原始数据类型
TError 默认 DefaultError 查询出错时的错误类型,默认 Error 体系
TData 默认 TQueryFnData 经过 select 转换后暴露给使用者的数据类型
TQueryKey extends QueryKey,默认 QueryKey 查询键类型,决定缓存条目归属

在实际调用中,多数时候你并不需要显式传入这些泛型参数——它们由 queryFn 的返回值、queryKey 的结构以及 select 函数自动推导。TData 默认为 TQueryFnData,只有当你在查询配置里使用 select 对数据做投影转换时,TData 才会与 TQueryFnData 分道扬镳;而返回的查询结果类型也随之变化。

四、与 DefinedInitialDataOptions 的对照:何时用哪个

query-options.ts 中并列定义了三种针对普通查询的 options 形态,它们的差异全部集中在"初始数据与 data 类型"这一维度:

类型别名 initialData queryFn 结果 data 是否含 undefined
UndefinedInitialDataOptions 可选(可省略或为 undefined 继承 CreateQueryOptions,可省略 undefinedCreateQueryResult
DefinedInitialDataOptions 必填 可选,声明为 QueryFunction 不含 undefinedDefinedCreateQueryResult
UnusedSkipTokenOptions 剔除 SkipToken 后的普通 queryFn 用于 skipToken 条件查询场景

这种划分不是为了制造冗余类型,而是 TanStack Query 对"数据是否必然存在"的类型级承诺:只要你在配置中提供了 initialData,编译器就能保证消费方拿到的 data 永远是明确的;反之若 initialData 可缺省,消费方就必须处理 data === undefined 的 pending 状态。

五、在源码调用链中生效:injectQuery 的重载解析

UndefinedInitialDataOptions 并非孤立类型,它直接参与 injectQuery 函数签名的重载解析。查看 inject-query.ts:65-129 可以看到两组对照重载:

// 重载 A:提供 initialData → 结果 data 必然存在
export function injectQuery<
  TQueryFnData = unknown, TError = DefaultError,
  TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey,
>(
  injectQueryFn: () => DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
  options?: InjectQueryOptions,
): DefinedCreateQueryResult<TData, TError>

// 重载 B:initialData 可能缺失 → 结果 data 可能为 undefined
export function injectQuery<
  TQueryFnData = unknown, TError = DefaultError,
  TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey,
>(
  injectQueryFn: () => UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
  options?: InjectQueryOptions,
): CreateQueryResult<TData, TError>

运行时实现 inject-query.ts:218-225 是统一的——通过 createBaseQuery(injectQueryFn, QueryObserver) 创建 QueryObserver

export function injectQuery(
  injectQueryFn: () => CreateQueryOptions,
  options?: InjectQueryOptions,
) {
  !options?.injector && assertInInjectionContext(injectQuery)
  return runInInjectionContext(options?.injector ?? inject(Injector), () =>
    createBaseQuery(injectQueryFn, QueryObserver),
  ) as unknown as CreateQueryResult
}

也就是说,同样的运行时逻辑,仅凭传参的静态类型不同,TypeScript 就替你选择了不同的返回类型:传入含必填 initialData 的配置对象时,重载 A 命中,返回的 data 类型不含 undefined;传入 initialData 可省略的配置对象时,重载 B 命中,返回的 data 类型包含 undefined。这正是 UndefinedInitialDataOptions 在真实 API 表面的价值所在——它是重载分发与结果类型收窄的"路标"。

类型层面的这一行为在 inject-query.test-d.ts 中有成体系的编译期测试印证:

// initialData 以对象字面量给出 → TData 恒为已定义类型
const { data } = injectQuery(() => ({
  queryKey: ['todos'],
  queryFn: () => fetchTodos(),
  initialData: { wow: true },
}))
// data 中不包含 undefined

// initialData 未提供 → data 的类型联合中包含 undefined
const { data } = injectQuery(() => ({
  queryKey: ['todos'],
  queryFn: () => fetchTodos(),
}))
// data: { wow: boolean } | undefined

// initialData 为"可能返回 undefined"的函数 → data 同样包含 undefined
const { data } = injectQuery(() => ({
  queryKey: ['todos'],
  queryFn: () => fetchTodos(),
  initialData: () => undefined as { wow: boolean } | undefined,
}))

这些用例表明:库对"函数形态的 initialData 是否安全"的判定取决于函数返回类型中是否残留 undefined,与传入值本身是否为函数无关。若函数恒返回非空值,类型上仍等价于 DefinedInitialDataOptions 场景,测试中亦提供了 isSuccess 收窄后的类型验证。

六、queryOptions() 三重重载:类型原样返回、标签增强

UndefinedInitialDataOptions 还出现在工具函数 queryOptions() 的重载签名中(同文件 query-options.ts:76-146)。queryOptions 用于"以类型安全方式共享、复用查询选项",其核心特征是:运行时原样返回传入对象query-options.test.ts 断言 queryOptions(object)).toBe(object) 完全等价),但在编译期根据传入对象归属的类型返回"被打上 queryKey 数据标签"的对应类型:

export function queryOptions<
  TQueryFnData = unknown, TError = DefaultError,
  TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey,
>(
  options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> &
  QueryKeyWithDataTag<TQueryKey, TQueryFnData, TError>

QueryKeyWithDataTag 会把 queryFn 推导出的数据类型"标记"进 queryKey 的类型里,此后无论把这份 options 传给 injectQueryQueryClient.getQueryData 还是传给子组件复用,都能保持数据类型的单向流动与一致性。query-options.ts 中共有三个并列的重载,分别对应 DefinedInitialDataOptionsUnusedSkipTokenOptions 与本文主角 UndefinedInitialDataOptions,编译器会依据你传入对象的 initialData/queryFn 形态自动命中合适分支。

七、实际使用建议与小结

结合类型定义与源码行为,在使用时可以把 UndefinedInitialDataOptions 相关的决策归纳为三条实践准则:

  1. 无初始数据需求时无需显式写类型injectQuery(() => ({ queryKey, queryFn })) 返回的对象天然归属 UndefinedInitialDataOptions 分支,data 类型含 undefined,请通过 isPending()/isSuccess()(类型收窄方法定义于 types.ts:51-73)或模板 @if 分流后使用。
  2. 需要"首屏有数据"时请使用必填的 initialData。此时类型系统自动切换为 DefinedInitialDataOptions 语义,data 不含 undefined,可省去判空。
  3. queryOptions() 提取共享配置。无论走哪个分支,复用 queryOptions 打包的选项都能获得 queryKey 数据标签增强,且由于运行时是原样透传,不会引入任何额外开销;UndefinedInitialDataOptions 与它的"必填兄弟" DefinedInitialDataOptions、以及无限查询版的 UndefinedInitialDataInfiniteOptions.md 共同构成了 TanStack Query Angular 对"数据是否必然存在"的完整类型契约体系。

总而言之,UndefinedInitialDataOptions 表面上只是一个 CreateQueryOptions & object 的组合类型,实际却是驱动 injectQueryqueryOptions 类型收窄与重载分发的关键一环。读懂它,就拿到了 TanStack Query Angular 在编译期管理"可能为空的数据"这把钥匙——它让运行时状态(pending、success、error)在类型层面就与代码路径一一对应,而不是等到页面渲染时才用非空断言掩盖风险。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388