在TypeScript的switch语句中使用const枚举和推断类型索引接口

时间:2019-05-29 18:00:22

标签: typescript enums switch-statement type-inference

我试图根据满足switch条件的enum值自动推断在case语句中传递的某些数据的类型。

为此,我定义了一个const枚举:

const enum MESSAGES {
  open = 1,
  close,
  redo
}

然后我用它来索引Interface

interface MessagePayloadContent {
  [MESSAGES.open]: string;
  [MESSAGES.close]: number;
  [MESSAGES.redo]: boolean;
}

这时,我定义了将在switch语句中评估的对象:

interface MessagePayload<T extends MESSAGES> {
  scope: T;
  content: MessagePayloadContent[T];
}

最后,我在switch语句中使用了以上内容。

我希望解释器可以基于传递给每个case的值来推断object中将包含什么类型的数据。

相反,出现以下代码中的注释中的错误:

function pick(payload: MessagePayload<MESSAGES>): void {
  switch (payload.scope) {
    case MESSAGES.open:
      open(payload.content); // Argument of type 'string | number | boolean' is not assignable to parameter of type 'string'.
      break;
    case MESSAGES.close:
      close(payload.content as number); // This is my current workaround.
      break;
    case MESSAGES.redo:
      redo(payload.content); // Argument of type 'string | number | boolean' is not assignable to parameter of type 'boolean'.
      break;
  }
}

const open = (d: string) => d;
const close = (d: number) => d;
const redo = (d: MessagePayloadContent[MESSAGES.redo]) => d;

我不能完全理解的是,在某些其他情况下,这种方法确实有效,所以我想知道为什么在其他情况下,这种方法不起作用。

1 个答案:

答案 0 :(得分:1)

这里的问题是类型MessagePayload<MESSAGES>不是您认为的那样。接口通常不会在联合上分布,因此其求值结果为:

interface OopsMessagePayload {
  scope: MESSAGES;
  content: string | number | boolean;
}

这意味着,如果您检查scope属性,它不会缩小content属性的类型。

TypeScript确实具有一些通过联合分布的类型级别的构造,因此应该有一种定义

的方法。
type MessagePayloadDistributive<T> = ...

如此

MessagePayloadDistributive<MESSAGES> 

评估为

MessagePayload<MESSAGES.open> |
MessagePayload<MESSAGES.closed> |
MessagePayload<MESSAGES.redo> 

我将使用distributive conditional types,如果您有一个类似type D<T> = T extends U ? V : W的类型,其中选中的类型T是一个裸类型参数,则条件检查将分布在各个并集上:

type MessagePayloadDistributive<T extends MESSAGES> = T extends any
  ? MessagePayload<T>
  : never;

type SomeMessagePayload = MessagePayloadDistributive<MESSAGES>;

如果您进行检查,则SomeMessagePayload会得出上述所需的类型。然后以下代码将按您期望的方式工作:

function pick(payload: SomeMessagePayload): void {
  switch (payload.scope) {
    case MESSAGES.open:
      open(payload.content); // okay
      break;
    case MESSAGES.close:
      close(payload.content); // okay
      break;
    case MESSAGES.redo:
      redo(payload.content); // okay
      break;
  }
}

好的,希望能有所帮助。祝好运!

Link to code