我试图将类型定义添加到一些使用通过Web Worker传递消息的代码中。调度的消息具有字符串类型的成员,可以在运行时使用它们来区分它们。
// One type of Message
export interface IOpenFileWMsg {
type: "OpenFileWMsg"
}
// Another type of message
export interface ICreateFileWMsg {
type: "CreateFileWMsg"
}
// Disjoint union containing all types of messages
export type IWMsg = IOpenFileWMsg | ICreateFileWMsg
// Helper type to extract a type given its type identifier:
// Eg. ISpecializedWMsg<"OpenFileWMsg"> == OpenFileWMsg
//
// [R1]
export type ISpecializedWMsg<T extends IWMsg["type"]> = Extract<
IWMsg,
{ type: T }
>
// Function which can handle a message of a specific type
//
// [R2]
export interface IWMsgHandler<T extends IWMsg["type"]> {
(msg: ISpecializedWMsg<T>): void
}
// Type of a dictionary of all handlers
export type IWMsgHandlers = {
[K in IWMsg["type"]]: IWMsgHandler<K>
}
const handlers: IWMsgHandlers = {
OpenFileWMsg(_event: IOpenFileWMsg) {},
CreateFileWMsg(_event: ICreateFileWMsg) {},
}
// Handle a general message:
const handleMessage = <T extends IWMsg["type"]>(msg: ISpecializedWMsg<T>) => {
const type: T = msg.type
// [R3]
const handler: IWMsgHandler<T> = handlers[type]
handler(msg)
}
上面的行[R3]给了我以下错误:
Type 'IWMsgHandlers[T]' is not assignable to type 'IWMsgHandler<T>'.
Type 'IWMsgHandler<"OpenFileWMsg"> | IWMsgHandler<"CreateFileWMsg">' is not assignable to type 'IWMsgHandler<T>'.
Type 'IWMsgHandler<"OpenFileWMsg">' is not assignable to type 'IWMsgHandler<T>'.
Types of parameters 'msg' and 'msg' are incompatible.
Type 'Extract<IOpenFileWMsg, { type: T; }> | Extract<ICreateFileWMsg, { type: T; }>' is not assignable to type 'IOpenFileWMsg'.
Type 'Extract<ICreateFileWMsg, { type: T; }>' is not assignable to type 'IOpenFileWMsg'.
Type '{ type: T; } & ICreateFileWMsg' is not assignable to type 'IOpenFileWMsg'.
Types of property 'type' are incompatible.
Type 'T & "CreateFileWMsg"' is not assignable to type '"OpenFileWMsg"'.
Type '"CreateFileWMsg"' is not assignable to type '"OpenFileWMsg"'. [2322]
我想了解为什么这不起作用,以及这是否是TypeScript类型系统的已知限制。
到目前为止,我能做到的最好的是:
const handler = handlers[type] as IWMsgHandler<any>
在这种情况下,这种向上转换不会导致类型安全性损失,但是我仍然很好奇为什么原始方法无效。
答案 0 :(得分:2)
当你说
const handleMessage = <T extends IWMsg["type"]>
当IWMsg
展开时,它变得简单
const handleMessage = <T extends 'OpenFileWMsg' | 'CreateFileWMsg'>
您的意思是T
可以是'OpenFileWMsg'
或'CreateFileWMsg'
。
但这不是编译器解释它的方式。对于编译器,字面意思是any type that extends either 'OpenFileWMsg' or 'CreateFileWMsg'
您可能会认为这与'OpenFileWMsg' | 'CreateFileWMsg'
没什么不同,但是在javascript中,字符串是对象,并且类型系统必须正确建模,前提是确实可以扩展'OpenFileWMsg'
之类的字符串文字类型
顺便说一下,这是可以扩展的方式:
type X = 'OpenFileWMsg' & { foo: string };
let x: X = Object.assign('OpenFileWMsg', { foo: 'bar' });
const type: IWMsg['type'] = x;
因此,在此实现中
const handleMessage = <T extends IWMsg["type"]>(msg: ISpecializedWMsg<T>) => {
const type: T = msg.type
// [R3]
const handler: IWMsgHandler<T> = handlers[type]
handler(msg)
}
不能保证handers[type]
可分配给IWMsgHandler<T>
,因为允许T
是扩展IWMsg["type"]
的任何类型,而不仅仅是工会的单个成员。
您必须使用类型断言:
const handleMessage = <T extends IWMsg["type"]>(msg: ISpecializedWMsg<T>) => {
const type: T = msg.type
// [R3]
const handler = handlers[type] as IWMsgHandler<T>;
handler(msg)
}
我记得在TypeScript github上看到一个功能请求或错误,有人在其中要求表达一种类型必须恰好是联合的一个成员的能力,但我现在找不到。
注意,此答案说明关闭--strictFunctionTypes
时将收到的错误。启用--strictFunctionTypes
时,错误将有所不同,因为编译器将遇到另一种不兼容性,请参见Titian的答案对此进行解释。
答案 1 :(得分:2)
由于编译器无法了解函数内部的T
的特定类型,因此T
基本上等效于两个可能的值"OpenFileWMsg" | "CreateFileWMsg"
(以及从它们派生的任何值),如果扩展双方得到的类型:
typeof handlers[type]
= typeof handlers[IWMsg["type"]];
= IWMsgHandler<"OpenFileWMsg"> | IWMsgHandler<"CreateFileWMsg">
= ((msg: IOpenFileWMsg) => void) | ((msg: ICreateFileWMsg) => void)
IWMsgHandler<IWMsg["type"]>
= IWMsgHandler<"OpenFileWMsg" | "CreateFileWMsg">
= (msg: IOpenFileWMsg | ICreateFileWMsg) => void
因此,基本上,您是将可以采用IOpenFileWMsg
的函数或可以采用ICreateFileWMsg
的函数分配给可以采用IOpenFileWMsg
或ICreateFileWMsg
的函数< / p>
一个为什么不安全的简化示例是:
function takesNumber(n: number){}
function takesSting(s: string){}
let takesEither : (sn: number| string)=> void = takesNumber; // Invalid
takesEither("") // This would be a runtime error