如何在抽象父类中为实现打字稿创建通用方法?

问题描述

在 TypeScript 中实现这一点的最佳和(适当)方法是什么? $Foo.getInstance('uid') 应该根据实现返回 FooInstance 吗?

我希望在抽象类 Entity 中有一个从池中获取实例的方法, 用于返回已实现的 EntityInstance

abstract class Entity {
    abstract Instance: Partial<typeof EntityInstance>;
    instances: { [uid: string]: EntityInstance } = {};
    getInstance (uid: string ) {
        return this.instances[uid]
    }
}
abstract class EntityInstance {
    prop='';
}

class Foo extends Entity {
    Instance = FooInstance // @implementation
}
class FooInstance extends EntityInstance {

}


const $Foo = new Foo();
// need return InstanceType<FooInstance>
const instance = $Foo.getInstance('uid'); 

这里的例子: const instance = $Foo.getInstance('uid') 应该是 FooInstance; 但它实际上是一个 EntityInstance 是正确的!

enter image description here

所以我试着把这个方法 getInstance 改成这样。

    getInstance <t=this>(uid: string ): InstanceType<this['Instance']> {
        return this.instances[uid]
    }

它可以工作!:)但会产生一些错误类型。 我是 ts 文档的菜鸟,我可以进行哪些更改以使此逻辑正常工作。 我知道 ts 很强大,但我不知道如何在我的 ide 中使这个工作正常进行智能。

enter image description here


最小繁殖演示typescript 我希望 myInstance.__foo2; 不产生错误

解决方法

您可以尝试将 Entity 中的类型更改为以下内容:

abstract class Entity {
    abstract Instance: new () => EntityInstance;

    instances: Record<string,InstanceType<this["Instance"]> | undefined
    > = {};
}

Instance 属性是一个 constructor,它返回一个 EntityInstance(或其子类型)。而 instances 属性类型取决于 Instance 的类型;通过使用 polymorphic this,我们表示对于 Entity 的任何子类,instances 属性将取决于同一子类中 Instance 的类型。

这为您提供了您正在寻找的行为,至少对于示例代码:

class Foo extends Entity {
    Instance = FooInstance;
}
class FooInstance extends EntityInstance {
    __foo2 = 2;
}

const $Foo = new Foo();
$Foo.instances['myUid'] = new $Foo.Instance();

const myInstance = $Foo.instances['myUid'];
myInstance.__foo2; // okay

请注意,在子类本身内部使用多态 this 类型可能有点麻烦:

class Foo extends Entity {
    Instance = FooInstance;

    constructor() {
        super();
        this.instances.abc = new FooInstance(); // error!
        this.instances.abc = new this.Instance(); // error!
    }

}

我不确定您是否需要做这样的事情,但是尝试在 this.instances 内的 Foo 上设置属性失败,因为编译器不知道 this 会做什么如果有人出现并子类 Foo。它将 this 视为未指定的泛型类型,并且无法真正验证是否可以为其分配任何特定值。在这种情况下,您可能需要使用 type assertions 来抑制错误。


另一种方法是使 Entity 成为 generic class,其中类型参数 T 对应于子类中 EntityInstance 的特定子类型:

abstract class Entity<T extends EntityInstance> {
    abstract Instance: new () => T;
    instances: Record<string,T | undefined> = {};
}

任何特定的子类都需要指定 T 应该是什么(这有点多余),但随后一切正常......子类内部和外部:

class Foo extends Entity<FooInstance> {
    Instance = FooInstance;
    constructor() {
        super();
        this.instances.abc = new FooInstance(); // okay
        this.instances.abc = new this.Instance(); // okay
    }
}

myInstance.__foo2;  // still okay

Playground link to code