如何在双泛型参数函数中透明处理联合类型?
TypeScript中处理Factory联合类型的runFactory调用问题
现有Factory<A, B>类型定义,以及用于执行它的runFactory函数。当尝试将runFactory应用于返回Factory<string, number> | Factory<number, string>联合类型的生成器输出时,TypeScript会抛出类型不兼容错误,尽管代码实际运行正常。
报错代码
type Factory<A, B> = { makeValue: () => A, process: (a: A) => B } const createFactory = (): Factory<string,number> | Factory<number,string> => Math.random() < 0.5 ? { makeValue: () => document.title, process: (s: string) => s.length} : { makeValue: () => Math.random(), process: (n: number) => `${n}`} const runFactory = <A, B>({makeValue, process}: Factory<A, B>): B => process(makeValue()) console.log(runFactory(createFactory()))
错误提示
Argument of type 'Factory<string, number> | Factory<number, string>' is not assignable to parameter of type 'Factory<string, number>'. Type 'Factory<number, string>' is not assignable to type 'Factory<string, number>'. The types returned by 'makeValue()' are incompatible between these types. Type 'number' is not assignable to type 'string'.
限制条件
- 不能在
runFactory内部使用类型守卫,因为Factory的泛型参数是未知的,类型本身应该自包含逻辑; - 必须保持强类型,禁止使用
any或unknown; - 单泛型参数的调用方式(如
runFactory<string | number>(createFactory()))无法满足需求,必须同时处理两个泛型参数的关联关系。
解决方案
TypeScript无法自动对联合类型的Factory进行分布式类型推导,我们可以通过分布式条件类型让runFactory正确识别联合类型中每个分支的返回值:
type Factory<A, B> = { makeValue: () => A, process: (a: A) => B } const createFactory = (): Factory<string, number> | Factory<number, string> => Math.random() < 0.5 ? { makeValue: () => document.title, process: (s: string) => s.length} : { makeValue: () => Math.random(), process: (n: number) => `${n}`} // 定义分布式条件类型,从Factory联合类型中提取返回值类型 type ExtractFactoryReturn<T> = T extends Factory<infer _, infer B> ? B : never; // 修改runFactory的泛型定义,支持联合类型输入 const runFactory = <T extends Factory<any, any>>(factory: T): ExtractFactoryReturn<T> => { const { makeValue, process } = factory; // 这里的类型断言是安全的,因为ExtractFactoryReturn已经正确推导了类型 return process(makeValue()) as ExtractFactoryReturn<T>; } // 此时调用会自动推导返回值为 string | number console.log(runFactory(createFactory()))
另一种更简洁的重载写法,同样可以实现强类型推导:
// 重载签名:处理单个Factory类型 function runFactory<A, B>(factory: Factory<A, B>): B; // 重载签名:处理Factory联合类型 function runFactory<T extends Factory<any, any>>(factory: T): T extends Factory<infer _, infer B> ? B : never; // 实现逻辑 function runFactory(factory: Factory<any, any>) { return factory.process(factory.makeValue()) } console.log(runFactory(createFactory()))
这两种方案都不需要类型守卫或any/unknown,同时保持了强类型检查,TypeScript会正确推导最终返回值为string | number,符合代码的实际运行逻辑。
内容的提问来源于stack exchange,提问作者Aleksandar Dimitrov
相关产品推荐
相关产品推荐

