您希望调用listen<MenuChannels>(⋯)
,并使返回类型仍然依赖于其输入类型(例如,listen<MenuChannels>('menu', '1')
是类型"3"
,而listen<MenuChannels>('menu-2', '5')
是类型"5"
). 这意味着您希望listen<MenuChannels>
仍然是generic,以便编译器可以为name
和event
的输入推断出泛型类型参数K1
和K2
,并使用它计算返回类型.
但你现在不能在TypeScript中这样做. TypeScript缺少microsoft/TypeScript#26242中讨论的所谓partial type argument inference.现在,如果你有一个泛型函数,比如listen()
,你手动指定了一个类型参数,比如listen<MenuChannels>(⋯)
,那么你必须手动指定all个类型参数.你不能只指定T
,然后让编译器推断K1
和K2
.相反,如果你想要推断,你需要让编译器推断all个类型参数,包括T
.类型参数推理要么全部要么没有.
在实现部分类型参数推理之前,您需要解决它.标准的解决方法是使用currying作为间接,以便listen<MenuChannels>()
返回另一个泛型函数,该函数可以为您推断K1
和K2
. 它将采用以下形式:
declare function listen<T extends WebsocketChannel[]>():
<K1 extends ???, K2 extends ???>
(name: K1, event: K2) => ???;
你可以这样称呼它:
listen<MenuChannels>()('menu', '1')
或者,如果你可能经常使用listen<MenuChannels>
,你可以写道:
const listenMenuChannels = listen<MenuChannels>();
listenMenuChannels('menu', '1');
listenMenuChannels('menu-2', '5');
如果没问题的话,我们可以继续.
为了使这一点更简单,将T extends WebsocketChannel[]
转换为一个对象类型会很有帮助,在这个类型中,你可以将它与K1
和K2
进行index into,以获得所需的返回类型. 我将编写一个名为WebsocketChannelLookup<T>
的实用程序类型,目的是获得MenuChannels
的以下输出:
type WebsocketChannelLookupMenuChannels =
WebsocketChannelLookup<MenuChannels>;
/* type WebsocketChannelLookupMenuChannels = {
menu: {
1: "3";
2: "4";
};
"menu-2": {
5: "7";
6: "8";
};
} */
有了这个类型,你可以看到listen<MenuChannels>()('menu', '1')
对应于WebsocketChannelLookupMenuChannels["menu"]["1"]
.
下面是实用程序类型实现:
type WebsocketChannelLookup<T extends WebsocketChannel[]> = {
[U in T[number] as U['channel']]: {
[E in U['events'][number] as E['name']]:
E['value']
}
}
这使用key remapping in mapped types来覆盖union个数组成员类型(T[number]
是T
数组的元素类型,因为如果你索引到一个带有数字索引的数组中,你会得到一个元素). 我们使用channel
成员作为键,对于值,我们类似地遍历events
成员的元素以获得值.
有了这个,现在listen()
看起来像:
declare function listen<T extends WebsocketChannel[]>():
<K1 extends keyof WebsocketChannelLookup<T>, K2 extends keyof WebsocketChannelLookup<T>[K1]>
(name: K1, event: K2) => WebsocketChannelLookup<T>[K1][K2];
所以我们取T
,将其转换为WebsocketChannelLookup<T>
,然后进行嵌套索引.
让我们来测试一下:
const listenMenuChannels = listen<MenuChannels>();
const x = listenMenuChannels('menu', '1');
// ^? const x: "3"
const y = listenMenuChannels('menu-2', '5')
// ^? const y: "7"
看上go 不错.这就是您想要的行为(只要您不介意由于缺少微软/TypeScrip#26242而需要额外的间接性).
Playground link to code