You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript联合类型属性推断与Angular表单类型适配问题

解决Angular DeepFormGroup处理判别式联合类型数组时的类型推断问题

问题复现

先还原你的场景代码,明确实际与预期的类型差异:

import { FormControl, FormGroup, FormArray } from '@angular/forms';

// 原DeepForm类型定义
type DeepFormArray<T> = FormArray<DeepFormGroup<T>>;
type DeepFormGroup<T> = {
  [K in keyof T]: T[K] extends Array<infer U> 
    ? DeepFormArray<U> 
    : T[K] extends object 
      ? DeepFormGroup<T[K]> 
      : FormControl<T[K]>;
};

// 带字符串字面量判别属性的联合类型
type A = { type: 'a'; propA: string };
type B = { type: 'b'; propB: number };
type C = A | B;

// 包含联合类型数组的接口
interface D {
  prop: C[];
}

// TS推断出的实际类型:两个FormArray的联合
type ActualType = DeepFormGroup<D>;
// 等价于:{ prop: DeepFormArray<A> | DeepFormArray<B> }

// 你期望的类型:包含合并后类型的单一FormArray
type NeededType = {
  prop: FormArray<DeepFormGroup<{ 
    type: 'a' | 'b';
    propA?: string;
    propB?: number;
  }>>;
};

原因分析

TypeScript的分布式条件类型特性导致了这个问题:当映射类型(比如DeepFormGroup里的[K in keyof T])遇到联合类型时,会自动遍历联合的每个成员单独处理,再把结果重新组合成联合。对于数组类型C[],TS会分别处理A[]和B[],生成DeepFormArray<A>和DeepFormArray<B>,最终形成联合类型,而非合并A|B后生成单一的DeepFormArray<A|B>。

解决方案

我们需要先定义一个工具类型,将判别式联合类型合并为保留判别属性的交叉类型(判别属性保留联合,其余属性转为可选),再修改DeepFormArray的逻辑,优先处理联合类型的数组元素。

步骤1:定义联合合并工具类型

// 合并判别式联合类型:保留判别属性的联合,其余属性转为可选交叉
type MergeUnionWithDiscriminant<T, Discriminant extends keyof T> = 
  T extends infer U 
    ? { [K in Discriminant]: U[K] } & Partial<Omit<T, Discriminant>> 
    : never;

// 自动提取联合类型的判别属性(这里假设判别属性为`type`,可根据实际调整)
type DiscriminantKey<T> = T extends { type: infer _ } ? 'type' : never;

步骤2:修改DeepForm类型定义

调整DeepFormArray,当数组元素是带判别属性的联合类型时,先合并再生成FormArray:

import { FormControl, FormGroup, FormArray } from '@angular/forms';

// 重复上面的MergeUnionWithDiscriminant和DiscriminantKey类型

type DeepFormArray<T> = FormArray<
  T extends object 
    ? (DiscriminantKey<T> extends never 
      ? DeepFormGroup<T> 
      : DeepFormGroup<MergeUnionWithDiscriminant<T, DiscriminantKey<T>>>
    )
    : FormControl<T>
>;

type DeepFormGroup<T> = {
  [K in keyof T]: T[K] extends Array<infer U> 
    ? DeepFormArray<U> 
    : T[K] extends object 
      ? DeepFormGroup<T[K]> 
      : FormControl<T[K]>;
};

步骤3:验证结果

现在重新推断ActualType,就会和你期望的NeededType完全一致:

type ActualType = DeepFormGroup<D>;
// 等价于:{ prop: FormArray<DeepFormGroup<{ type: 'a' | 'b'; propA?: string; propB?: number }>> }

通用化调整(可选)

如果你的判别属性不是固定的type,可以改成支持手动传入判别属性名的版本:

// 通用版合并工具类型
type MergeUnionWithDiscriminant<T, Discriminant extends keyof T> = 
  T extends infer U 
    ? { [K in Discriminant]: U[K] } & Partial<Omit<T, Discriminant>> 
    : never;

// 通用版DeepForm类型
type DeepFormArray<T, Discriminant extends keyof T | never = never> = FormArray<
  T extends object 
    ? (Discriminant extends never 
      ? DeepFormGroup<T> 
      : DeepFormGroup<MergeUnionWithDiscriminant<T, Discriminant>>
    )
    : FormControl<T>
>;

type DeepFormGroup<T, Discriminant extends keyof any = never> = {
  [K in keyof T]: T[K] extends Array<infer U> 
    ? DeepFormArray<U, Discriminant> 
    : T[K] extends object 
      ? DeepFormGroup<T[K], Discriminant> 
      : FormControl<T[K]>;
};

// 使用时指定判别属性名
type ActualType = DeepFormGroup<D, 'type'>;

内容的提问来源于stack exchange,提问作者Hodossy Szabolcs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 21:07:33