约束成员只接受扩展或实现其他类型 [TypeScript] 的类型(而非实例)

Constraints a member to just accept a type (not an instance) that extends or implements other type [TypeScript]

我需要一些关于打字稿抽象的帮助。

我想限制成员只接受类型作为值,但这些类型需要实现或扩展其他类型或接口。

例如

以下示例不代表或与真实数据模型相关,而是一个说明目标的示例。

interface Profession {
  work()
}

class Engineer implements Profession {
  work() {...}
}

class Doctor  {
  work() {...}
}

interface ProfessionRankingMap {
  top1ProfessionType: // Here is where I don't know how to constraint
  top2ProfessionType: // Here is where I don't know how to constraint
}

const classAProfessionRankingMap: ProfessionRankingMap {
  // This is okay, Type Engineer implements Profession interface
  top1ProfessionType: Engineer
  // This is bad, Type Doctor doesn't implement Profession interface
  top2ProfessionType: Doctor
}

const classBProfessionRankingMap: ProfessionRankingMap {
  // This is bad, [new Engineer()] returns an instance not a type
  top1ProfessionType: new Engineer()
  // This is bad, [new Doctor()] returns an instance not a type
  top2ProfessionType: new Doctor()
}

要在类型中表达一个class,你需要一个构造函数签名:

interface ProfessionRankingMap {
  // The implementer must have a constructor with no args that returns Profession 
  top1ProfessionType: new () => Profession 
  top2ProfessionType: new () => Profession
}

const classAProfessionRankingMap: ProfessionRankingMap = {
  top1ProfessionType: Engineer,
  top2ProfessionType: Doctor
}

Playground link

如果我们想接受 class 具有任意数量参数的构造函数,我们可以使用 new (... args:any[]) => Profession.

你的问题的第二部分有点复杂。 Typescript 使用结构类型来确定类型兼容性。所以 class 的结构与 implements 子句无关。 implements 子句只会在您声明 class 时帮助您出错,确保它具有所有必需的成员,因此您会尽早收到错误,而不是在您尝试使用 class 时预期界面。

这意味着只要 Doctor 拥有 Profession 的所有成员,就没有办法阻止它被传递到预期 Profession 的地方。

唯一的解决方案是使用带有私有成员的抽象 class。私有成员将确保除了扩展抽象 class 的那些 class 之外,没有其他 class 与抽象 class 类型兼容:

abstract class Profession {
  private _notStructural: undefined;
  abstract work(): void
}

class Engineer extends Profession {
    work() { }
}

class Doctor  {
    work() { }
}

interface ProfessionRankingMap {
  top1ProfessionType: new () => Profession
  top2ProfessionType: new () => Profession
}

const classAProfessionRankingMap: ProfessionRankingMap = {
  top1ProfessionType: Engineer,
  top2ProfessionType: Doctor // error
}

Playground link