Задача вывода/сужения типа TypeScript

В настоящее время я пытаюсь улучшить типы в некотором существующем коде. Мой код выглядит примерно так:

/* dispatcher.ts */
interface Message {
    messageType: string;
}

class Dispatcher<M extends Message> {    
    on<
        MessageType extends M["messageType"],
        SubMessage extends M & { messageType: MessageType }
    >(
        messageType: MessageType,
        handler: (message: SubMessage) => void
    ): void { }
}

/* messages.ts */
interface AddCommentMessage {
    messageType: "ADD_COMMENT";
    commentId: number;
    comment: string;
    userId: number;
}

interface PostPictureMessage {
    messageType: "POST_PICTURE";
    pictureId: number;
    userId: number;
}

type AppMessage = AddCommentMessage | PostPictureMessage;

/* app.ts */
const dispatcher = new Dispatcher<AppMessage>();

dispatcher.on("ADD_COMMENT", (message: AddCommentMessage ) => {
                                    /* ^^ REMOVE THIS TYPE HINT!*/
    console.log(message.comment);
});

Я хотел бы устранить необходимость явно сужать тип сообщения, передаваемого обработчику сообщений (где /*REMOVE THIS TYPE HINT!*/), чтобы он правильно сужался до типа, который имеет соответствующий тип messageType (например, если messageType равно "ADD_COMMENT", то message должен быть AddCommentMessage).

Если это невозможно прямо сейчас, пожалуйста, дайте мне знать. У меня сложилось впечатление, что это не так, но я не совсем уверен.


person Pat    schedule 20.03.2017    source источник
comment
Добро пожаловать в Stack Overflow! Предоставьте весь соответствующий код в минимально воспроизводимом примере в самом вопросе, а не только на стороннем сайте.   -  person Heretic Monkey    schedule 21.03.2017


Ответы (1)


Это невозможно, если вы не готовы немного изменить код.

Ваш базовый интерфейс

interface Message {
    messageType: string;
}

слишком общий, я думаю, что messageType: string исключает любой вывод, основанный на значении messageType, и похоже, что невозможно достаточно сузить его в интерфейсе Dispatcher.

Если вы ограничиваете код только AppMessage и его потомками, вот пример того, как сделать typescript для вывода нужных вам типов, руководствуясь типами строковых литералов (keyof AppMessageMap на самом деле является объединением типов строковых литералов "ADD_COMMENT" | "POST_PICTURE"):

/* dispatcher.ts */

class Dispatcher {    
    on<
        MessageType extends keyof AppMessageMap
    >(
        messageType: MessageType,
        handler: (message: AppMessageMap[MessageType] & {messageType: MessageType}) => void
    ): void { }
}

/* messages.ts */
interface AddCommentMessage {
    commentId: number;
    comment: string;
    userId: number;
}

interface PostPictureMessage {
    pictureId: number;
    userId: number;
}

interface AppMessageMap {
    "ADD_COMMENT": AddCommentMessage,
    "POST_PICTURE": PostPictureMessage
}
type AppMessage = AppMessageMap[keyof AppMessageMap];

/* app.ts */
const dispatcher = new Dispatcher();


dispatcher.on("ADD_COMMENT", (message) => {
    console.log(message.comment);
});

Я также удалил свойство messageType из интерфейсов, чтобы избежать дублирования, я думаю, что тип пересечения в параметре handler дает тот же эффект.

person artem    schedule 20.03.2017
comment
Это идеальный ответ, большое спасибо. Меня всегда поражает, насколько мощной является система типов TypeScript. Единственное, что я изменил, это сделать Dispatcher универсальным, чтобы AppMessageMap передавалось как параметр типа (я использую один и тот же Dispatcher для нескольких приложений в одной кодовой базе). - person Pat; 21.03.2017