Индексируйте интерфейс с помощью константного перечисления и типа вывода в операторе switch в TypeScript - PullRequest
0 голосов
/ 29 мая 2019

Я пытаюсь автоматически определить тип некоторых данных, передаваемых в операторе switch, на основе значения enum, удовлетворяющего условию case.

Для этого я определил константное перечисление:

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 Ответ

1 голос
/ 29 мая 2019

Проблема здесь в том, что тип 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> 

Я буду использовать дистрибутивные условные типы , в которых, если у вас есть тип, такой как 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;
  }
}

Хорошо, надеюсь, это поможет.Удачи!

Ссылка на код

...