首页 > 解决方案 > 使用实例类型带工厂功能

问题描述

我有一个相当简单的工厂函数,它使用构造函数映射来根据传入的字符串参数确定要创建的正确类型的对象。

我知道示例中描述的将类构造函数传递给工厂的工厂模式,但在我的情况下,我需要传入一个简单的字符串。

class Vehicle {
    public wheels: number;
}
class Car extends Vehicle {
    public drive: number
}
class Bike extends Vehicle {
    public ride: number;
}

const CTORS = {
    car: Car,
    bike: Bike
}

type VehicleTypes = typeof CTORS;

function factory<T extends keyof VehicleTypes>(name: T): InstanceType<VehicleTypes[T]> {
    let ctor: VehicleTypes[T] = CTORS[name];

    // un-comment to see error    
    // return new ctor();

    return new ctor() as InstanceType<VehicleTypes[T]>;
}

let abc = factory('bike');
abc.ride = 5;   // type checks ok

上述工作和类型检查正常,但返回时显式键入对于避免编译器错误是必要的:

(Type 'Car | Bike' is not assignable to type 'InstanceType<{ car: typeof Car; bike: typeof Bike; }[T]>'.  Type 'Car' is not assignable to type 'InstanceType<{ car: typeof Car; bike: typeof Bike; }[T])

我怀疑打字稿抱怨返回值不是所有潜在实例的联合。但是我不知道如何应用类型,这样就不需要显式的类型覆盖。我也尝试了以下方法,但也无济于事:

type ReturnTypes = { 
    [P in keyof VehicleTypes]: InstanceType<VehicleTypes[P]>
}
function factory<T extends keyof VehicleTypes>(name: T): ReturnTypes[T] {
    let ctor = CTORS[name];
    return new ctor();
}

标签: typescripttypescript-generics

解决方案


这是目前 TypeScript 的一个限制:未解析的条件类型,即那些依赖于尚未指定的泛型类型参数的类型,对编译器是不透明的;它不能真正看到可以分配给它的某些值。类型InstanceType<T>定义如下

type InstanceType<T extends new (...args: any) => any> = 
  T extends new (...args: any) => infer R ? R : any;

这是一个条件类型,的实现中factory(),类型InstanceType<VehicleTypes[T]>是未解析的,因为T没有指定。


有几个开放的 GitHub 问题提供了使此类未解决的条件类型更易于处理的建议,但目前都没有实现(截至 TS3.7)。如果您足够关心去那里并给他们一个或以其他方式支持他们,这里有一些链接到他们:


现在,我会说要么像你一样使用类型断言,要么找到一种不依赖条件类型的方式来表示你的类型。一种可能的前进方式是注意 TypeScript 中的类构造函数被赋予prototype与其实例类型相同类型的属性。这有点奇怪,也不是很正确,因为实际的原型不会有任何仅实例属性,但这就是它的方式,而且不太可能改变

InstanceType因此,您可以使用这个事实制作自己的:

type MyInstanceType<T extends { prototype: any }> = T['prototype'];

然后你可以写

function factory<T extends keyof VehicleTypes>(name: T): MyInstanceType<VehicleTypes[T]> {
    let ctor: VehicleTypes[T] = CTORS[name];
    return new ctor();
}

没有错误,你仍然得到你期望的类型检查:

let abc = factory('bike');
abc.ride = 5;   // type checks ok

希望有帮助;祝你好运!

链接到代码


推荐阅读