TypeScript type inference/narrowing challenge

你说的曾经没有我的故事 提交于 2019-12-01 17:16:53

问题


I'm currently trying to improve the types on some existing code. My code looks roughly like this:

/* 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);
});

I'd like to remove the need to explicitly narrow the type of the message passed to the message handler (where /*REMOVE THIS TYPE HINT!*/ is), such that it correctly narrows to the type that has a matching messageType type (e.g. if messageType is "ADD_COMMENT" then message should be AddCommentMessage).

If this is not possible right now please let me know. I'm under the impression that it isn't but I'm not quite sure.


回答1:


It's not possible, unless you are willing to change the code quite a bit.

Your base interface

interface Message {
    messageType: string;
}

is too general, I think messageType: string precludes any inference based on the value of the messageType, and looks like it's impossible to sufficiently narrow it in the Dispatcher interface.

If you limit the code to AppMessage and its descendants only, here is an example how to make typescript to infer the types you need, guided by string literal types (keyof AppMessageMap is actually a union of string literal types "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);
});

I also removed messageType property from the interfaces to avoid duplication, I think that intersection type in handler parameter achieves the same effect.



来源:https://stackoverflow.com/questions/42913985/typescript-type-inference-narrowing-challenge

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!