2017-05-09 254 views
1

this StackOverflow post爲例,我實現了一個類型化的事件系統。簡化的,它看起來像:在TypeScript中映射泛型函數中的映射類型

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; 
    } 
} 

打字稿的行爲時,該功能被外部調用預期。例如:

// 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無法正確推斷函數內部的類型,雖然它在外部調用中可以正常工作?

回答

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; 
    } 
} 

所以編譯器不限制的eventMsg類型時,變量的類型它取決於,eventKey,是受限制的。儘管如此,它會很好。但如果你願意使用一個將消息類型映射到一個處理程序而不是一個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 
});