仅键入 class 的子 classes

Type for only subclasses of a class

如果我有一个基础 class 和继承自该基础的多个子 class,例如:

class Message {
      constructor(public text: string) {}
}

class MessageA extends Message {
    msgType: string = 'A';

    constructor(
        public text: string,
        public aData: string
    ) {
        super(text);
    }
}

class MessageB extends Message {
    msgType: string = 'B';

    constructor(
        public text: string,
        public bData: number
    ) {
        super(text);
    }
}

…是否可以创建一个只接受子 classes 的类型?

我想要类似的东西

type Msg = MessageA | MessageB;

…这样我就可以写

const show = (msg: Msg): void => {
    switch (msg.msgType) {
        case 'A':
            return console.log(`${msg.text}: ${msg.aData}`);
        case 'B':
            return console.log(`${msg.text}: ${msg.bData}`);
    }
}

但是,上面的代码给我这个错误:

Property 'aData' does not exist on type 'Msg'.
  Property 'aData' does not exist on type 'MessageB'.

如果我将类型更改为以下,show 将再次起作用:

type Msg = ({msgType: 'A'} & MessageA)
         | ({msgType: 'B'} & MessageB);

但是如果我用新的 MessageB 调用 show...

show(new MessageB('hello', 300));

…我得到这个错误:

Argument of type 'MessageB' is not assignable to parameter of type 'Msg'.
  Type 'MessageB' is not assignable to type '{ msgType: "B"; } & MessageB'.
    Type 'MessageB' is not assignable to type '{ msgType: "B"; }'.
      Types of property 'msgType' are incompatible.
        Type 'string' is not assignable to type '"B"'.(2345)

为基 class 的子class创建类型的好方法是什么?

此处的目标是确保处理程序在访问任何字段(不在基础 class 中)之前检查传递给它的对象的类型,并确保 switch 在添加未来子 class 时是详尽无遗的。

看起来您希望 Msg 成为 discriminated unionmsgType 作为 判别式 属性。

很遗憾,您将 MessageAMessageB 上的 msgType 属性注释为 string,并且 string 不是有效的判别式 属性 类型。判别式必须是只接受单个值的单位类型,例如 undefinednull,或 string/numeric/boolean literal type.

而不是 string,您希望它们分别是字符串文字类型 "A""B"。您可以通过显式注释它们,或将它们标记为 readonly,这将导致编译器为它们推断文字类型:

class MessageA extends Message {
  readonly msgType = 'A';
  // (property) MessageA.msgType: "A"

  constructor(
    public text: string,
    public aData: string
  ) {
    super(text);
  }
}

class MessageB extends Message {
  readonly msgType = 'B';
  // (property) MessageB.msgType: "B"

  constructor(
    public text: string,
    public bData: number
  ) {
    super(text);
  }
}

执行此操作后,show() 函数中的缩小将起作用:

const show = (msg: Msg): void => {
  switch (msg.msgType) {
    case 'A':
      return console.log(`${msg.text}: ${msg.aData}`); // okay
    case 'B':
      return console.log(`${msg.text}: ${msg.bData}`); // okay
  } 
}

Playground link to code