【问题标题】:Inferring mapped type in a generic function in TypeScript在 TypeScript 的泛型函​​数中推断映射类型
【发布时间】:2017-05-09 20:46:58
【问题描述】:

this StackOverflow post 为例,我实现了一个类型化的事件系统。简化后看起来像:

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

我正在尝试使用此地图创建一个事件处理程序:

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 的行为与预期相同。例如:

// 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');

但是,TypeScript 函数内部很混乱。似乎相信任何案例陈述都不会受到打击。为什么 TypeScript 无法正确推断函数内部的类型,尽管它在外部调用中运行良好?

【问题讨论】:

    标签: generics typescript switch-statement typing inference


    【解决方案1】:

    目前没有办法根据另一个变量的类型来约束一个变量的类型。删除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;
      }
    }
    

    所以当它所依赖的变量eventKey 的类型受到限制时,编译器不会限制eventMsg 的类型。不过,如果可以的话,那就太好了。

    但是,如果您愿意使用将消息类型映射到处理程序而不是 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
    });
    

    【讨论】:

      猜你喜欢
      • 2020-09-09
      • 1970-01-01
      • 2019-09-17
      • 2016-12-05
      • 2023-02-26
      • 2020-07-23
      • 2017-06-02
      • 2016-11-01
      • 2017-10-04
      相关资源
      最近更新 更多