简体   繁体   English

在TypeScript中的通用函数中推断映射类型

[英]Inferring mapped type in a generic function in TypeScript

Using this StackOverflow post as an example, I implemented a typed event system. StackOverflow文章为例,我实现了一个类型化的事件系统。 Simplified, it looks like: 简化,看起来像:

interface MyTypeMap {
  FOO: string;
  BAR: number;
}

I'm trying to create an event handler utilizing this map: 我正在尝试使用此地图创建一个事件处理程序:

function handleEvent<T extends keyof MyTypeMap>(eventKey: T, eventMsg: MyTypeMap[T]) {
  switch(eventKey) {
    case('FOO'):
      // TS believes that eventKey is of type 'never'
      // TS believes that eventMsg is 'string|number'
      break;
    case('BAR'):
      // TS believes that eventKey is of type 'never'
      // TS believes that eventMsg is 'string|number'
      break;
  }
}

TypeScript behaves as expected when this function is called externally. 在外部调用此函数时,TypeScript的行为与预期的一样。 For example: 例如:

// These work as desired
handleEvent('FOO', 'asdf');
handleEvent('BAR', 5);

// These throw compile errors as desired
handleEvent('FOO', 6);
handleEvent('BAR', 'i am not a string');

However, inside the function TypeScript is very confused. 但是,函数TypeScript里面很混乱。 It seems to believe that none of case statements can ever get hit. 它似乎相信没有任何案件陈述会受到打击。 Why is TypeScript unable to properly infer the types inside the function, though it's working fine with external calls? 为什么TypeScript无法正确推断函数内部的类型,尽管它可以正常使用外部调用?

There is no way currently to constraint type of one variable based on the type of another variable. 目前没有办法根据另一个变量的类型约束一个变量的类型。 The best you can get after removing generic parameter T of handleEvent (no idea why it causes typescript to infer eventKey type as never ) is 删除handleEvent泛型参数T后你可以得到的最好(不知道为什么它会导致eventKey类型推断为never

function handleEvent(eventKey: keyof MyTypeMap, eventMsg: MyTypeMap[typeof eventKey]) {
  switch(eventKey) {
    case ('FOO'):
          const a1 = eventKey; // a1 has type 'FOO'
          const m1 = eventMsg; // m1 has type string|number
      break;
    case('BAR'):
          const a2 = eventKey; // a2 has type 'BAR'
          const m2 = eventMsg; // again, m2 has type string|number
      break;
  }
}

so the compiler does not restrict the type of eventMsg when the type of the variable it depends on, eventKey , is restricted. 因此,编译器不限制的类型eventMsg当它取决于变量的类型, eventKey ,被限制。 It woule be nice if it could, though. 尽管如此,它仍然很好。

But if you are willing to use an object that maps message type to a handler instead of a switch statement, here is working prototype (currently limited to have only one handler per message type, but could be easily extended I believe): 但是如果你愿意使用一个将消息类型映射到处理程序而不是switch语句的对象,那么这里是工作原型(目前仅限于每个消息类型只有一个处理程序,但我相信可以轻松扩展):

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

    handlerMap: {[s: string]: (message: AppMessageMap[keyof AppMessageMap]) => void} = {};

    emit<MessageType extends keyof AppMessageMap>(messageType: MessageType, message: AppMessageMap[MessageType]) {
        const handler = this.handlerMap[messageType];
        if (handler) {
            handler(message);
        }
    } 
}

/* messages.ts */

interface AddCommentMessage {
    commentId: number;
    comment: string;
    userId: number;
}

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

interface AppMessageMap {
    "ADD_COMMENT": AddCommentMessage,
    "POST_PICTURE": PostPictureMessage
}

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


dispatcher.on("ADD_COMMENT", (message) => {
    console.log(`add comment: ${message.comment}`);
});
dispatcher.on("POST_PICTURE", (message) => {
    console.log(`post picture: ${message.pictureId}`);
}); 

dispatcher.emit('ADD_COMMENT', {
    comment: 'some comment',
    commentId: 2,
    userId: 3
});
dispatcher.emit('POST_PICTURE', {
    pictureId: 4,
    userId: 5
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM