TypeScript映射类型中枚举数组解析为枚举成员数组联合类型的问题
TypeScript映射类型处理枚举数组的类型问题
我正在尝试实现一个TypeScript映射类型,用来从给定的模型类型生成Angular表单组类型。目前已经实现了检测数组属性并转为FormArray<T>的逻辑,但遇到了枚举数组的类型映射问题:当属性类型为MyEnum[]时,解析后的类型变成了MyEnum.A[] | MyEnum.B[],导致无法向数组中添加不同的枚举值。
简化问题复现代码
enum Environment{ "EN" = "en", "US" = "us", } interface MyDto { environments: Environment[]; } export type FormControlsOf<T> = { // 检查属性是否为数组 [TPropertyKey in keyof T]-?: T[TPropertyKey] extends Array<infer TArray> // 检查TArray是否为Record类型,以此决定是转为FormControl还是FormGroup(真实代码逻辑) ? TArray extends Record<any, any> ? // 示例中忽略Record类型 never // 将属性映射为数组;真实代码中会转为FormArray<FormControl<TArray>> : TArray[] // 示例中忽略非数组类型 : never; } const q: FormControlsOf<MyDto> = { environments: [Environment.EN, Environment.US] // environments的实际类型是 (Environment.EN[] | Environment.US[]) }; // 错误提示: // Type 'Environment[]' is not assignable to type 'Environment.EN[] | Environment.US[]'. // Type 'Environment[]' is not assignable to type 'Environment.EN[]'. // Type 'Environment' is not assignable to type 'Environment.EN'. // Type 'Environment.US' is not assignable to type 'Environment.EN'.(2322) // input.tsx(8, 3): 预期类型来自属性'environments',该属性在类型'FormControlsOf<MyDto>'上声明
问题补充说明
如果移除第13行对TArray的检查,environments的类型可以正确识别为Environment[],但我需要这个检查来判断属性应该转为FormArray<FormGroup>还是FormArray<FormControl>。
经@wonderflame和@Robby Cornelissen指出,分布式条件类型是问题的根源。
完整的Angular表单组映射类型代码
/** * 该映射类型用于生成对应模型类型的表单组表示类型 * * 示例: * interface MyDto { * prop1: string; * prop2: string[]; * } * * FormGroupOf<MyDto> 会被解析为: * { * prop1: FormControl<string>; * prop2: FormArray<FormControl<string>>; * } * * 这样可以定义完全类型化的表单字段 * * class TestClass { * * public form: FormGroupOf<MyDto>; * * constructor() { * this.form = new FormGroup({ * prop1: new FormControl(""), * prop2: new FormArray<FormControl<string>>([]), * }) * } * } * * 这种方式在模板和表单组构造过程中都能获得代码补全 * * 对于复杂属性类型,支持递归处理: * interface MyDto2 { * prop1: { * innerProp: string; * }; * } * * FormGroupOf<MyDto2> 会被解析为: * { * prop1: FormGroup<{ * innerProp: FormControl<string|null>; * }>; * } */ export type FormGroupOf<T> = FormGroup<FormControlsOf<T>>; export type FormControlsOf<T> = { [TPropertyKey in keyof T]-?: T[TPropertyKey] extends | (infer TArray)[] | undefined ? [TArray] extends [string | number | boolean | undefined] ? FormArray<FormControl<TArray>> : FormArray<FormGroupOf<TArray>> : T[TPropertyKey] extends string | number | boolean | undefined ? FormControl<T[TPropertyKey] | null> : FormGroupOf<T[TPropertyKey]>; };
解决方案:避免分布式条件类型的影响
问题核心在于,当TArray是枚举这类联合类型时,TArray extends XXX形式的条件类型会触发分布式特性——将联合类型的每个成员单独代入判断,最终返回成员类型数组的联合(如Environment.EN[] | Environment.US[]),而非我们期望的联合类型数组(Environment[])。
要解决这个问题,只需通过元组包裹的方式禁用条件类型的分布式特性:
修改后的简化版代码
export type FormControlsOf<T> = { [TPropertyKey in keyof T]-?: T[TPropertyKey] extends Array<infer TArray> // 用元组包裹TArray,阻止分布式条件类型展开 ? [TArray] extends [Record<any, any>] ? never : TArray[] : never; } // 此时 FormControlsOf<MyDto>['environments'] 的类型为 Environment[] const q: FormControlsOf<MyDto> = { environments: [Environment.EN, Environment.US] // 无类型错误 };
修改后的完整Angular表单组映射类型
将同样的逻辑应用到完整代码中,确保所有条件判断都避免分布式展开:
export type FormControlsOf<T> = { [TPropertyKey in keyof T]-?: T[TPropertyKey] extends | (infer TArray)[] | undefined // 用元组包裹TArray,避免拆分枚举联合类型 ? [TArray] extends [string | number | boolean | undefined] ? FormArray<FormControl<TArray>> : FormArray<FormGroupOf<TArray>> : [T[TPropertyKey]] extends [string | number | boolean | undefined] ? FormControl<T[TPropertyKey] | null> : FormGroupOf<T[TPropertyKey]>; };
修改后,枚举数组的类型会被正确识别为联合类型的数组,而非数组类型的联合,即可正常向数组中添加不同的枚举值。
内容的提问来源于stack exchange,提问作者Limeray
相关产品推荐
相关产品推荐

