Typescript 可以推断由其基础方法实例化的扩展实例 class 的类型吗?

Can Typescript infer the type of an instance of an extension class instantiated by a method of its base?

考虑以下 Typescript 片段:

class Animal {
  constructor(name: string) {
    this.name = name;
  }
  name: string;

  haveBaby(name: string): ?? return type ?? {
    return new this.constructor(name); // Error
  }
}

class Cat extends Animal {}
class Dog extends Animal {}
class Gerbil extends Animal {} // etc.

let sorachi = new Cat("Sorachi"); // a: Cat
let sorachiJr = a.haveBaby("Sorachi Jr."); // I want: sorachiJr: Cat

动物可以生孩子,婴儿应该是与 parent 相同种类的动物,即应该是与 parent 相同的 class 的实例。在这种情况下如何分配类型,以便 Typescript 知道 sorachiJr: Cat?

上面的代码片段不起作用。return new this.constructor(name) 在 VS Code 中产生错误 [ts] Cannot use 'new' with an expression whose type lacks a call or construct signature.。我能够找到并理解的唯一解决方案是将 this.constructor(name) 替换为 (<any>this.constructor)(name)(<any>this).constructor(name),但是 sorachiJr 的推断类型也是 any,而不是 Cat。我尝试转换为 typeof this 而不是 any,但出现错误 [ts] Cannot find name 'this'.

如何让 Typescript 相信生孩子是一项 species-preserving 操作?

保存 class 调用方法的类型很容易,我们只需使用多态 this 类型。要说服 ts constructor 将是一个构造函数,它采用 string 和 returns 与当前 class 类型相同的实例需要类型断言

type AnimalConstructor<T extends Animal> = new (name: string) => T
class Animal {
    constructor(name: string) {
        this.name = name;
    }
    name: string;

    haveBaby(name: string): this  {
        return new (this.constructor as AnimalConstructor<this>)(name); 
    }
}

class Cat extends Animal {}
class Dog extends Animal {}
class Gerbil extends Animal {} // etc.

let sorachi = new Cat("Sorachi"); // a: Cat
let sorachiJr = sorachi.haveBaby("Sorachi Jr."); 

注意 Typescript 无法验证派生的 class 构造函数只需要一个 string 参数的事实,它可能需要更多或更少的参数.这使得此构造函数类型不安全。