typescript - 尝试从联合中提取类型时出现类型错误
问题描述
我正在尝试将类型定义添加到一些使用通过网络工作者传递消息的代码中。分派的消息有一个字符串类型的成员,可以在运行时使用它来区分它们。
// 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"]>
扩展时IWMsg
变得简单
const handleMessage = <T extends 'OpenFileWMsg' | 'CreateFileWMsg'>
你的意思是T
可以是'OpenFileWMsg'
or 'CreateFileWMsg'
。
但这不是编译器解释它的方式。对于编译器,它的字面意思是any type that extends either 'OpenFileWMsg' or 'CreateFileWMsg'
你可能会认为这与 just 没有什么不同'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 的回答来解释这一点。
推荐阅读
- java - spring boot 2.1.7默认登录不成功
- html - 如何有意访问本地服务器上的“/索引”页面?
- php - 获取电影类型的输出
- reactjs - 找不到 webpack.config.dev.js 和 webpack.config.prod.js
- arkit - SCNSceneRenderer ARFaceAnchor 锚点检测距离
- asp.net-core - .Net Core 安装程序与 .Net Core 二进制文件有什么区别?
- python-3.x - 使用 Python 连接到 Netcool 中的 nco_sql
- python - Pandas 在一个数据帧上给出 IndexError 但在另一个类似的数据帧上没有
- vb.net - 在组合框中仅列出一次文件类型
- substrate - 无法编译节点运行时