如何在未知网关实现时定义TypeScript的UseCase类型?
TypeScript网关用例类型定义问题解决
问题代码与错误提示
用户编写的TypeScript代码如下:
class Gateway {} class GatewayA extends Gateway { open() {} } class GatewayB extends Gateway { } type UseCase<T> = (...gateways: Gateway[]) => () => Promise<T>; const gatewayA = new GatewayA; const gatewayB = new GatewayB; const fooUseCase: UseCase<boolean> = (gatewayA: GatewayA, gatewayB: GatewayB) => async () => { console.log(gatewayA); console.log(gatewayB); return true; }; fooUseCase(gatewayA, gatewayB);
TypeScript抛出的类型兼容错误:
Type '(gatewayA: GatewayA, gatewayB: GatewayB) => () => Promise<true>' is not assignable to type 'UseCase<boolean>'. Types of parameters 'gatewayA' and 'gateways' are incompatible. Property 'open' is missing in type 'Gateway' but required in type 'GatewayA'.ts(2322)
需求说明:网关类继承自Gateway基类,部分子类会新增方法;不同用例需要传入不同的网关实例(如fooUseCase需要GatewayA和GatewayB,其他用例可能需要GatewayC),需要在预先不知道具体网关实现的情况下,正确定义UseCase类型。
解决方案
核心问题是原UseCase类型固定参数为Gateway[],但实际用例需要更具体的网关子类,而函数参数的类型兼容性是逆变的——接受子类的函数无法赋值给接受父类的函数类型(因为父类没有子类的专属方法)。
我们可以通过给UseCase添加泛型参数,让它适配任意网关类型组合:
方法1:适配固定组合的网关元组
如果每个用例需要的网关类型是固定的组合(比如[GatewayA, GatewayB]这种明确的顺序和类型),可以定义接受网关元组的泛型:
class Gateway {} class GatewayA extends Gateway { open() {} } class GatewayB extends Gateway { } // 新增泛型G,约束为Gateway类型的元组 type UseCase<T, G extends Gateway[]> = (...gateways: G) => () => Promise<T>; const gatewayA = new GatewayA; const gatewayB = new GatewayB; // 方式1:让TypeScript自动推导泛型参数 const fooUseCase = (gatewayA: GatewayA, gatewayB: GatewayB) => async () => { console.log(gatewayA); // 可以正常调用gatewayA的open方法 gatewayA.open(); console.log(gatewayB); return true; }; // 方式2:显式指定泛型参数 const barUseCase: UseCase<boolean, [GatewayA, GatewayB]> = (gatewayA, gatewayB) => async () => { gatewayA.open(); return true; }; fooUseCase(gatewayA, gatewayB); barUseCase(gatewayA, gatewayB);
方法2:适配任意数量的同类型网关
如果用例只需要任意数量的同一种网关子类,可以这样定义:
type UseCase<T, G extends Gateway = Gateway> = (...gateways: G[]) => () => Promise<T>; // 示例:需要多个GatewayA的用例 const multiGatewayAUseCase: UseCase<boolean, GatewayA> = (...gateways) => async () => { gateways.forEach(g => g.open()); return true; };
原理说明
通过泛型参数G,我们让UseCase不再绑定固定的Gateway父类,而是可以适配任意符合Gateway子类约束的类型组合。TypeScript会自动推导每个用例对应的网关类型,既保证了类型安全(比如能正常调用子类的专属方法),又保留了灵活性,支持任意网关组合。
内容的提问来源于stack exchange,提问作者viniciusbo
相关产品推荐
相关产品推荐

