Сопоставление типа сообщения с типом полезной нагрузки

#typescript

#typescript

Вопрос:

Я пытаюсь создать API шины сообщений, где сообщения могут быть отправлены только с полезной нагрузкой определенного типа / формы.

В основном я делаю это для того, чтобы, когда я начинаю вводить вызов emitMessage (ниже), я хотел бы, чтобы IDE могла предоставлять подсказки типа во время компиляции для payload аргумента, как только я укажу type аргумент, чтобы я знал, какой тип payload ожидается для любого данного типа сообщения.

Я попытался выполнить это с помощью чего-то вроде следующего:

 // I create some generic payload interface that can be any shape
interface Payload<T> {
    [key: string]: any
}

// I define an enum of permitted message types
enum MessageType {
    TEST
}

// And then I expose a function for emitting messages of a particular type
// along with its associated payload
function emitMessage<Msg extends MessageType>(type: Msg, payload: Payload<Msg>) {
   // ...
}
  

Но этот код неполный / неправильный, потому что я понятия не имею, как бы я указал, что определенный вариант MessageType перечисления связан с определенной специализацией Payload .

Я не уверен, возможно ли это вообще, поэтому любая помощь в этом направлении была бы высоко оценена.

Комментарии:

1. Итак, вам нужен какой-то маршрутизатор, который в зависимости от типа сообщения отправляет полезную нагрузку какому-либо другому методу, который может с этим справиться?

2. @solarc Нет, возможно, моего объяснения было недостаточно. Когда я вызываю emitMessage, я бы хотел, чтобы IDE могла предоставлять подсказки типа во время компиляции для payload после того, как я указал type , имеет ли это смысл? Чтобы я знал, какая полезная нагрузка ожидается при предоставлении данного сообщения

Ответ №1:

Я нашел действительно классное решение в виде типов индексов

 // I define an enum of permitted message types
enum MessageType {
    TEST
}

// This interface is then used as a kind of "type directory"
interface Payload {
    [MessageType.TEST]: {
        test: boolean
    }
}

// payload's type here is now an index into the Payload type directory
function emitMessage<M extends MessageType>(msg: M, payload: Payload[M]) {
    switch(msg) {
        case MessageType.TEST: {
            // payload.test is suggested by my IDE now! 
        }
    }
}
  

Эта функция потрясающая, и я собираюсь использовать ее для всего

Ответ №2:

Один из подходов заключается в использовании различаемых союзов. Сначала добавьте в свою полезную нагрузку поле, указывающее ее тип, например kind beliow

 interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}
  

Сначала мы объявляем интерфейсы, которые будем объединять. Каждый интерфейс имеет свойство kind с другим типом строкового литерала. Свойство kind называется дискриминантом или тегом. Остальные свойства специфичны для каждого интерфейса. Обратите внимание, что интерфейсы в настоящее время не связаны. Давайте объединим их:

 type Shape = Square | Rectangle | Circle;
  

Теперь давайте используем различаемое объединение:

 function area(s: Shape) {
  switch (s.kind) {
      case "square": return s.size * s.size;
      case "rectangle": return s.height * s.width;
      case "circle": return Math.PI * s.radius ** 2;
  }
}
  

Смотрите раздел Расширенные типы