TypeScript:合并联合类型的泛型参数
我希望bar方法能自动合并Foo的泛型类型参数,否则调用baz方法会抛出如下错误:
Each member of the union type '(<TBaz extends string>(value: TBaz) => void) | (<TBaz extends number>(value: TBaz) => void)' has signatures, but none of those signatures are compatible with each other.ts(2349)
复现错误的代码
interface Foo<TX, TY> { bar<TFoo extends Foo<unknown, unknown>>(mapper: (value: TX) => TFoo): TFoo; baz<TBaz extends TY>(value: TBaz): void } const x = null as unknown as Foo<'1', number>; const y = x.bar(x => { if(x) { return null as unknown as Foo<'2', number>; } else { return null as unknown as Foo<'3', string>; } }); // y 的类型为 Foo<'2', number> | Foo<'3', string> // 但我们需要的类型是 Foo<'2' | '3', number | string> const z = y.baz('2'); // 这里会触发类型错误,虽然运行逻辑没问题
手动指定泛型Foo<'2'|'3',string|number>可以规避错误,但我希望TypeScript自动处理,无需每次显式指定。之前尝试用infer提取泛型参数重新构造类型,但没成功:
bar<TFoo extends Foo<unknown, unknown>>(mapper: (value: TX) => TFoo): TFoo extends Foo<infer X, infer Y> ? Foo<X, Y> : never;
解决方案
要实现自动合并泛型参数,需要调整bar方法的返回类型,让TypeScript把TFoo联合类型里的泛型参数分别提取出来,再合并成联合类型。可以通过分布式条件类型来实现:
方法一:定义类型工具复用逻辑
先写一个类型工具,专门处理Foo联合类型的泛型合并:
type MergeFooUnion<T extends Foo<unknown, unknown>> = Foo< // 提取所有Foo实例的TX参数并合并成联合类型 T extends Foo<infer X, unknown> ? X : never, // 提取所有Foo实例的TY参数并合并成联合类型 T extends Foo<unknown, infer Y> ? Y : never >;
然后修改Foo接口的bar方法,返回类型改成这个工具类型:
interface Foo<TX, TY> { bar<TFoo extends Foo<unknown, unknown>>(mapper: (value: TX) => TFoo): MergeFooUnion<TFoo>; baz<TBaz extends TY>(value: TBaz): void }
方法二:直接内联类型逻辑
如果不想单独定义类型工具,也可以把合并逻辑直接写在bar的返回类型里:
interface Foo<TX, TY> { bar<TFoo extends Foo<unknown, unknown>>( mapper: (value: TX) => TFoo ): Foo< TFoo extends Foo<infer X, unknown> ? X : never, TFoo extends Foo<unknown, infer Y> ? Y : never >; baz<TBaz extends TY>(value: TBaz): void }
测试效果
修改后再运行之前的代码:
const x = null as unknown as Foo<'1', number>; const y = x.bar(x => { if(x) { return null as unknown as Foo<'2', number>; } else { return null as unknown as Foo<'3', string>; } }); // y 的类型自动变为 Foo<'2' | '3', number | string> const z = y.baz('2'); // 不再触发类型错误,类型推导正常
原理说明
分布式条件类型会自动遍历联合类型中的每个成员,分别提取X和Y参数,最终把所有提取到的X合并成一个联合类型,Y同理,再用这两个联合类型构造出新的Foo类型。这样bar的返回值就不再是Foo的联合类型,而是泛型参数合并后的单一Foo类型,调用baz时自然不会出现签名不兼容的问题。
内容的提问来源于stack exchange,提问作者Benjamin M

