首页
/ 深入解析 Angular Query 的 CreateBaseMutationResult:Mutation 结果对象的信号化类型骨架

深入解析 Angular Query 的 CreateBaseMutationResult:Mutation 结果对象的信号化类型骨架

2026-09-07 13:34:07作者:温艾琴Wonderful

导读

CreateBaseMutationResult 是 TanStack Query 的 Angular 适配包(packages/angular-query-experimental)中,用于刻画一次 mutation 完整返回结果的核心类型别名。它把 query-core 中面向框架无关的 MutationObserverResult 改造成 Angular 响应式语境下可直接消费的形状——同步的 mutate 触发器、返回 PromisemutateAsync,以及后续可被收窄的状态字段。阅读本篇后,你将理解 injectMutation() 的返回值类型从何而来、四个泛型参数分别控制什么、mutate/mutateAsync 的类型差异,以及为什么该类型会作为更高层 CreateMutationResult 与状态收窄能力的构建基石。

本文的官方类型文档位于 CreateBaseMutationResult.md,其完整实现定义在 types.ts

一、类型定位:mutation 结果类型的"基座"

在 Angular Query 的类型体系中,mutation 相关的类型不止一个,它们之间的关系是层层组合的:

类型 文件位置 职责
CreateBaseMutationResult types.ts 最底层的结果形状:状态字段 + mutate/mutateAsync 调用方法
CreateStatusBasedMutationResult types.ts Extract<..., { status: TStatus }>status 从结果中筛出某种具体状态下的变体
CreateMutationResult types.ts 面向用户最终暴露的结果:在基座上叠加 BaseMutationNarrowing 收窄守卫并映射为信号
CreateMutationOptions types.ts mutation 的配置输入类型

也就是说,CreateBaseMutationResult 定义了"一次 mutation 运行后你手里到底有什么",而 CreateMutationResult 只是在其之上补充了 isPending/isError/isSuccess/isIdle 等 TypeScript 类型谓词,并决定哪些字段以 Angular Signal 形式暴露。

injectMutation() 函数的官方签名返回值正是组合后的结果:

function injectMutation<TData, TError, TVariables, TOnMutateResult>(
  injectMutationFn: () => CreateMutationOptions<TData, TError, TVariables, TOnMutateResult>,
  options?: { injector?: Injector },
): CreateMutationResult<TData, TError, TVariables, TOnMutateResult>

可参见 injectMutation 的参考文档,与类型定义文件 inject-mutation.ts 完全一致。

二、类型签名逐行拆解

类型文档给出的别名签名为:

type CreateBaseMutationResult<TData, TError, TVariables, TOnMutateResult> = Override<
  MutationObserverResult<TData, TError, TVariables, TOnMutateResult>,
  {
    mutate: CreateMutateFunction<TData, TError, TVariables, TOnMutateResult>;
  }
> & object;

仓库中 实际定义 略微展开,把 & object 声明为显式的成员:

export type CreateBaseMutationResult<
  TData = unknown,
  TError = DefaultError,
  TVariables = unknown,
  TOnMutateResult = unknown,
> = Override<
  MutationObserverResult<TData, TError, TVariables, TOnMutateResult>,
  { mutate: CreateMutateFunction<TData, TError, TVariables, TOnMutateResult> }
> & {
  mutateAsync: CreateMutateAsyncFunction<
    TData,
    TError,
    TVariables,
    TOnMutateResult
  >
}

这里有两个来自 query-core 的底层工具类型值得注意:

  • MutationObserverResult<TData, TError, TVariables, TOnMutateResult>:query-core 中 mutation 观察者对外呈现的结果,包含 datavariableserrorstatusisPending/isError/isSuccess/isIdle 派生布尔值,以及 mutate 方法等,具体字段见 query-core 的 types.ts
  • Override<A, B>:把 A 中与 B 重叠的键用 B 覆盖。此处的作用是用 Angular 化的 CreateMutateFunction 替换掉 query-core 中框架无关的 mutate 字段类型,相当于"替换式的类型改造",而其余字段原样保留。

mutateAsync 是类型声明中单独声明的必选成员,与文档 Type Declaration 一节展示的一致:

mutateAsync: CreateMutateAsyncFunction<TData, TError, TVariables, TOnMutateResult>;

因此一个完整的 mutation 结果对象在类型上保证同时具备两个"开火"入口:mutatemutateAsync

三、mutate 与 mutateAsync:两种调用口径

这一对方法承担不同职责,分别由两个专门类型刻画,官方参考见 CreateMutateFunction.mdCreateMutateAsyncFunction.md

1. CreateMutateFunction:同步"即发即忘"的触发函数

定义于 types.ts

export type CreateMutateFunction<
  TData = unknown,
  TError = DefaultError,
  TVariables = void,
  TOnMutateResult = unknown,
> = (
  ...args: Parameters<MutateFunction<TData, TError, TVariables, TOnMutateResult>>
) => void

它直接复用 query-core MutateFunction 的参数列表,但把返回类型定为 void——这是刻意为之的:mutate 面向模板与事件回调,调用后并不等待结果、也不暴露 Promise,非常适合"按钮点击后触发、状态由 signals 驱动渲染"的响应式场景。

运行时印证在 inject-mutation.ts:实现里把底层的 observer.mutate(...) 包成同步触发、错误被 noop 吞掉的封装:

const mutateFnSignal = computed(() => {
  const observer = observerSignal()
  return (...args: Parameters<CreateMutateFunction<...>>) => {
    observer.mutate(args[0] as TVariables, args[1]).catch(noop)
  }
})

2. CreateMutateAsyncFunction:返回 Promise 的异步入口

定义于 types.ts,它没有重新发明轮子,而是直接等于 query-core 的 MutateFunction

export type CreateMutateAsyncFunction<
  TData = unknown,
  TError = DefaultError,
  TVariables = void,
  TOnMutateResult = unknown,
> = MutateFunction<TData, TError, TVariables, TOnMutateResult>

query-core 中 MutateFunction 的真实形状(types.ts)为:

export type MutateFunction<
  TData = unknown,
  TError = DefaultError,
  TVariables = void,
  TOnMutateResult = unknown,
> = (
  ...rest: MutateFunctionRest<TData, TError, TVariables, TOnMutateResult>
) => Promise<TData>

注意它返回 Promise<TData>,因此 mutateAsync 适合在服务、效果回调或组合逻辑中配合 await/try-catch 使用,能拿到成功数据或捕获失败。

值得补充的是参数元组 MutateFunctionResttypes.ts)对 TVariables 的约束逻辑:

type MutateFunctionRest<TData, TError, TVariables, TOnMutateResult> =
  undefined extends TVariables
    ? [variables?: TVariables, options?: MutateOptions<...>]
    : [variables: TVariables, options?: MutateOptions<...>]
  • TVariablesvoid 或包含 undefined 时,variables 参数可省略(例如"无参数提交"的删除操作);
  • 否则 variables 为必填,类型安全地强制你传入变量对象。

在运行时,结果对象的 mutateAsync 直接取自 query-core 观察者原始结果中的 promise 型 mutate(见 inject-mutation.ts):

const result = resultFromSubscriber ?? resultFromInitialOptions
return {
  ...result,
  mutate: mutateFnSignal(),          // 同步即发即忘封装
  mutateAsync: result.mutate,        // 保留原始 Promise 版本
}

3. 两者在实践中的分工

特征 mutate mutateAsync
类型 CreateMutateFunction CreateMutateAsyncFunction(即 MutateFunction
返回 void Promise<TData>
错误处理 内部 .catch(noop),错误交由观察者/信号通道处理 返回 rejected Promise,可用 try/catch 自行接管
典型场景 模板事件、响应式触发 组合逻辑、依赖成功后串联、需要拿返回值

四、四个泛型参数:默认值与含义

类型别名携带四个泛型参数,文档逐一列出,其默认值与其"基座"身份相称:

参数 默认值 含义
TData unknown mutation 成功后 data 的类型(即 mutationFn 的返回值)
TError DefaultError 失败时 error 的类型,DefaultErrorError 的子类约束
TVariables unknown 提交给 mutationFn 的变量对象的类型
TOnMutateResult unknown onMutate 乐观更新回调返回的"上下文"类型,可在 onError/onSettled 中回滚

一个微妙但重要的设计:CreateBaseMutationResultTVariables 默认是 unknown(因为结果对象的 variables 字段只有在真正提交后才能确定),而作为方法签名类型的 CreateMutateFunction/CreateMutateAsyncFunctionTVariables 默认是 void(保证"无变量"调用合法)。这体现了两者在类型上各自的关注点:结果描述已发生的事,方法签名约束将要发生的事

五、从基座到顶层:与 CreateMutationResult 的组合关系

CreateBaseMutationResult 单独看只是静态形状,真正面向开发者的 CreateMutationResulttypes.ts 定义:

export type CreateMutationResult<
  TData = unknown,
  TError = DefaultError,
  TVariables = unknown,
  TOnMutateResult = unknown,
  TState = CreateStatusBasedMutationResult<
    CreateBaseMutationResult['status'], TData, TError, TVariables, TOnMutateResult
  >,
> = BaseMutationNarrowing<TData, TError, TVariables, TOnMutateResult> &
  MapToSignals<OmitKeyof<TState, keyof BaseMutationNarrowing, 'safely'>>

逐段解读:

  1. CreateBaseMutationResult['status'] 取出基座的 status 键作为约束,再由 CreateStatusBasedMutationResult 通过 Extract 选出与当前 status 匹配的精确状态子集——这是 Angular Query 做 discriminated-union 收窄的关键(官方文档见 CreateMutationResult.md)。
  2. BaseMutationNarrowingtypes.ts)定义了四个 TypeScript 类型谓词 isSuccessisErrorisPendingisIdle,每个都形如 (this: CreateMutationResult<...>) => this is CreateMutationResult<..., CreateStatusBasedMutationResult<'success', ...>>,据此在 if 分支内自动收窄出对应状态的类型。
  3. MapToSignals<T> 来自 signal-proxy.ts
    export type MapToSignals<T> = {
      [K in keyof T]: T[K] extends Function ? T[K] : Signal<T[K]>
    }
    
    它把所有非函数字段映射为 Angular Signal(读取时用 mutation.data()),而函数字段原样保留(mutation.isPending() 直接调用)。配合 signalProxy 代理(signal-proxy.ts),运行时把 mutation 结果中的每个字段包装成 computed 信号。
  4. OmitKeyof<TState, keyof BaseMutationNarrowing, 'safely'> 从状态中剔除四个收窄守卫键,避免与顶层谓词重复冲突。

类型层面有编译期测试兜底,例如 mutation-options.test-d.tsCreateMutationResult<string, DefaultError, void, unknown> 进行了类型断言,确保泛型接线正确。

六、实战视角:injectMutation 的结果如何被消费

综合以上类型设计,在组件中注入 mutation 时:

import { injectMutation } from '@tanstack/angular-query-experimental'

@Component({ /* ... */ })
export class TodosComponent {
  private readonly todosService = inject(TodosService)

  // 返回类型即 CreateMutationResult<Todo, DefaultError, string, unknown>
  readonly addTodo = injectMutation(() => ({
    mutationFn: (title: string) => this.todosService.add(title),
    onSuccess: () => this.todosService.invalidateList(),
  }))
}

在模板或代码中读取结果时,遵循"函数字段直接调用、普通字段通过信号读取"的约定:

  • 状态收窄:this.addTodo.isPending()this.addTodo.isSuccess()this.addTodo.isError()this.addTodo.isIdle()——它们是被保留的类型谓词函数;
  • 数据与错误:this.addTodo.data()this.addTodo.error()this.addTodo.variables()this.addTodo.status()——它们是被映射为 Signal 的字段;
  • 触发提交:在事件处理中调用 this.addTodo.mutate(newTitle)(同步、即发即忘),或在需要串联副作用时 await this.addTodo.mutateAsync(newTitle)

这种"一个 mutation 结果里既有信号化状态、又有两种触发方式"的类型契约,正是由 CreateBaseMutationResult 这一基座类型通过 Override& 交叉与信号映射层层搭建出来的。理解它,也就理解了 injectMutation() 返回值在模板中为何"既能读取、又能调用"。

七、小结

  • CreateBaseMutationResult 是 Angular Query mutation 结果类型的底层骨架,定义于 types.ts,文档见 CreateBaseMutationResult.md
  • 它用 Override 把 query-core 的 MutationObserverResult 中的 mutate 替换为 Angular 化同步版本,并声明了 promise 型的 mutateAsync
  • 四个泛型 TData/TError/TVariables/TOnMutateResult 分别控制数据、错误、提交变量与乐观更新上下文,默认值依次为 unknown/DefaultError/unknown/unknown
  • 它是 CreateStatusBasedMutationResultCreateMutationResultBaseMutationNarrowing 收窄体系的输入来源;运行时由 injectMutationsignalProxy 将其实例化为信号化对象。
  • 若要继续追踪兄弟类型,可对照同目录下的 CreateMutationResult.mdCreateMutateFunction.mdCreateMutateAsyncFunction.md
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
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