TypeScript泛型类型检查传入枚举时改变类型行为问题
问题现象
当使用T extends object的条件类型处理枚举(或字符串联合类型)时,得到的类型是AltFormValue<枚举成员1> | AltFormValue<枚举成员2>的联合类型,而非期望的AltFormValue<整个枚举类型>,导致调用$setValue时出现类型不匹配错误:
x2.Val.$setValue(TestEnum.T1)报错,因为x2.Val是AltFormValue<TestEnum.T1> | AltFormValue<TestEnum.T2>,而非AltFormValue<TestEnum>x4.$setValue(TestEnum.T1)和x7.$setValue(TestEnum.T1)同理
根本原因:分布式条件类型
TypeScript的条件类型有个特性:当泛型参数是联合类型时,会自动触发分布式条件类型——把联合类型的每个成员单独代入条件计算,再将结果合并成新的联合类型。
拿TestEnum来说,它本质是TestEnum.T1 | TestEnum.T2的联合。当执行T extends object时,每个枚举成员都是字符串字面量(不属于object类型),所以会走else分支,分别对TestEnum.T1和TestEnum.T2应用AltFormValue<NonUndefined<T>>,最终得到的是两个AltFormValue的联合,而非针对整个枚举类型的AltFormValue。
解决方案:阻止类型分发
要避免分布式条件类型,只需把泛型参数包裹在元组或对象中,让整个联合类型作为一个整体参与条件判断,而不是逐个拆分成员。
修改AltFormData和Alt2FormData的条件判断为[T] extends [object]:
type NonUndefined<T> = T extends undefined ? never : T; enum TestEnum { T1 = "T1", T2 = "T2", } interface Data { Val: TestEnum; } // 用元组包裹阻止分发 type AltFormData<T> = [T] extends [object] ? { readonly [P in keyof T]-?: AltFormData<NonUndefined<T[P]>>; } & AltFormValue<NonUndefined<T>> : AltFormValue<NonUndefined<T>>; type Alt1FormData<T> = AltFormValue<NonUndefined<T>>; // 同样修改条件判断 type Alt2FormData<T> = [T] extends [object] ? AltFormValue<NonUndefined<T>> : AltFormValue<NonUndefined<T>>; type AltFormValue<T = any> = { readonly $setValue: (value: T | undefined) => void; }; // 注:这里用as any是为了跳过空对象的初始化报错,不影响类型检查 const x2: AltFormData<Data> = {} as any; x2.Val.$setValue(TestEnum.T1); // 类型正常 const x3: AltFormValue<TestEnum> = {} as any; x3.$setValue(TestEnum.T1); // 正常 const x4: AltFormData<TestEnum> = {} as any; x4.$setValue(TestEnum.T1); // 类型正常 const x5: AltFormValue<NonUndefined<TestEnum>> = {} as any; x5.$setValue(TestEnum.T1); // 正常 const x6: Alt1FormData<TestEnum> = {} as any; x6.$setValue(TestEnum.T1); // 正常 const x7: Alt2FormData<TestEnum> = {} as any; x7.$setValue(TestEnum.T1); // 类型正常
原理说明
当用[T] extends [object]时,泛型参数T被包裹在元组里,即使T是联合类型,整个元组[T]会被当作单一类型判断,不会触发分发。此时TestEnum作为整体判断不属于object,直接返回AltFormValue<NonUndefined<TestEnum>>,也就是我们期望的类型。
同样的修改也适用于字符串联合类型(比如"T1" | "T2"),因为本质都是联合类型的分发问题。
内容的提问来源于stack exchange,提问作者Zvonimir Matic

