目前(截至TS 5.1)control flow analysis不影响generic个类型参数.因此,在类型<K extends keyof CharacterEvents>(eventName: K, payload: CharacterEvents[K]) => void
的函数中,判断eventName
函数参数可以缩小其表观类型,但不会对类型参数K
做任何操作.因此,payload
的类型也不受影响,并且不推断eventName
和payload
之间的相关性.他们被当作独立的人对待.
如果是eventName === "wait"
,这并不意味着K
已经从K extends keyof CharacterEvents
重新constrained变成K extends "wait"
.事实上,这样做在技术上是不正确的,因为K
总是有可能是wider,而不是"等待":
liToAll(Math.random() > 0.01 ? "wait" : "speak", { message: "abc" }); // okay
这里K
已经被推断为完整的union type keyof CharacterEvents
.此外,eventName
为"wait"
而payload
始终为{message: string}
的可能性为99%,因此payload.turns
在运行时未定义的可能性为99%.哎呀.看起来eventName
和payload
实际上是aren't,保证以您想要的方式关联(尽管这种事情很可能不太可能,而且TypeScrip的其他部分也做出了技术上不正确但非常方便的假设).
这意味着您想要的行为在这里不涉及常规的泛型约束.相反,它将需要某种新的约束,因此这是语言中缺失的一个特征.问题microsoft/TypeScript#27808要求一个这样的功能,这样你就可以说K oneof keyof CharacterEvents
这样的话,而不是K extends keyof CharacterEvents
,这意味着K
只能由keyof CharacterEvents
中的一个成员指定.那么,也许勾选eventName === "wait"
将允许将payload
缩小到{turns: number}
.
但就目前而言,它根本不是语言的一部分.
这就是问题的答案,但谈一谈另一种方法可能是相关的.目前,当您判断一个变量时,控制流分析将缩小另一个变量的范围的唯一方法是通过destructured discriminated unions.因此,如果eventName
和payload
被认为是discriminated union类型的某个值的属性,它将开始工作.
区分联合需要文字类型的区分属性...幸运的是,eventName
‘S类型是字符串类型"wait"
或"speak"
.由于eventName
和payload
是函数参数,因此可以将它们视为函数rest parameter的0
和1
属性.也就是说,我们希望您的调用签名为以下非泛型类型:
(...args:
[eventName: "wait", payload: { turns: number; }] |
[eventName: "speak", payload: { message: string; }]
) => void
我们可以重写ListenerToAll
实用程序类型,以生成这样一个有区别的联合REST参数类型,如下所示:
export type ListenerToAll<E> =
(...args: { [K in keyof E]:
[eventName: K, payload: E[K]]
}[keyof E]) => void;
这是一张distributive object type(在microsoft/TypeScript#47109中创造的).让我们来测试一下:
const liToAll: ListenerToAll<CharacterEvents> = (event, payload) => {
if (event === 'wait') {
console.log(payload.turns) // okay
}
}
这是现在汇编的;万岁!之前打给Math.random()
的电话现在被拒绝了:
liToAll(Math.random() > 0.01 ? "wait" : "speak", { message: "abc" }); // error
也太棒了!所以这大概是我能想象到的最接近你想要的行为了.元组联合作为REST参数类型的事情有点奇怪,但它是有效的.
最后,如果您希望在不使用liToAll
类型的annotating的情况下编写此代码,则需要将实现的函数参数显式地作为REST参数编写,可能如下所示:
const liToAll = (...[event, payload]:
[event: "wait", payload: { turns: number; }] |
[event: "speak", payload: { message: string; }]
) => {
if (event === 'wait') {
console.log(payload.turns)
}
}
Playground link to code个