Typescript 尝试从联合中提取类型时出现类型错误
我正在尝试将类型定义添加到一些使用通过web workers传递消息的代码中。调度的消息有一个字符串类型的成员,使用该成员可以在运行时对其进行区分Typescript 尝试从联合中提取类型时出现类型错误,typescript,Typescript,我正在尝试将类型定义添加到一些使用通过web workers传递消息的代码中。调度的消息有一个字符串类型的成员,使用该成员可以在运行时对其进行区分 // One type of Message export interface IOpenFileWMsg { type: "OpenFileWMsg" } // Another type of message export interface ICreateFileWMsg { type: "CreateFileWMsg" }
// 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>
在这种情况下,这种向上转换不会导致类型安全性的损失,但我仍然很好奇为什么原来的方法不起作用 当你说
const handleMessage = <T extends IWMsg["type"]>
您的意思是T可以是“OpenFileWMsg”或“CreateFileWMsg”
但编译器不是这样解释的。对于编译器来说,它字面上是指扩展“OpenFileWMsg”或“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)
}
我记得在TypeScript github上看到一个功能请求或bug,有人要求能够表示一个类型必须恰好是一个联合体的一个成员,但我现在找不到它
注意:此答案解释了关闭-strictFunctionTypes时将出现的错误。当-strictFunctionTypes打开时,错误会有所不同,因为编译器将遇到另一个不兼容问题,请参阅Titian的答案解释。因为编译器无法知道函数中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
不确定我是否真的同意您的解释,根据您的推理,这不应该起作用,但它确实起作用了:类型fns=fn1 | fn2 let test={fn1:s:number=>{},fn2:s:number=>{}}函数带有test k:T{let testFn:s:number=>void=test[k];testFn0}实际上是“OpenFileWMsg”&{foo:string}将可用作键并产生正确的结果,派生类型在需要基类型时可用。在操场上,错误消息中的最后一行是类型“OpenFileWMsg”不可分配给类型“T”。。我试图解释为什么它是不可分配的。请注意,这是在strictFunctionTypes已关闭的情况下进行的。抱歉,您是对的,în non strick解释是正确的。我的否决票被锁定了,如果你修改答案,我可以重新公布。我的错
const handleMessage = <T extends IWMsg["type"]>(msg: ISpecializedWMsg<T>) => {
const type: T = msg.type
// [R3]
const handler = handlers[type] as IWMsgHandler<T>;
handler(msg)
}
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
function takesNumber(n: number){}
function takesSting(s: string){}
let takesEither : (sn: number| string)=> void = takesNumber; // Invalid
takesEither("") // This would be a runtime error