[英]How to define a TypeScript type with a variable number of generics
I'm attempting to make an emit
function that can accept multiple arguments.我正在尝试制作一个可以接受多个参数的
emit
函数。 Furthermore, the 2nd argument and beyond will be validated by TypeScript based on the 1st argument (the event).此外,TypeScript 将根据第一个参数(事件)验证第二个参数及以后的参数。
I have the code below as a starting point, but it obviously doesn't work as intended.我将下面的代码作为起点,但它显然不能按预期工作。
type CallbackFunc<T extends any[]> = (...args: T) => void;
interface TrackablePlayerEventMap {
'play:other': CallbackFunc<['audio' | 'video']>;
error: CallbackFunc<[Error, Record<string, unknown>]>;
}
const eventHandlers: Partial<TrackablePlayerEventMap> = {};
function on<K extends keyof TrackablePlayerEventMap>(event: K, callback: TrackablePlayerEventMap[K]) {
}
function emit<K extends keyof TrackablePlayerEventMap>(event: K, ...args: TrackablePlayerEventMap[K]) {
const handler = eventHandlers[event];
handler(...args);
}
on('error', (e, metadata) => {
console.log(e, metadata)
});
on('play:other', x => {
console.log(x);
});
// This should be valid
emit('error', new Error('Ouch'), { count: 5 });
// This should be invalid
emit('error', 123, 456);
In order to write handler(...args)
once inside the body of emit()
and have it compile with no errors, you need to make use of the technique introduced in microsoft/TypeScript#47109 as a fix for an issue I've called "correlated unions", as mentioned in microsoft/TypeScript#30581 .为了在
emit()
的主体内编写handler(...args)
并让它编译时没有错误,您需要利用microsoft/TypeScript#47109中引入的技术来解决我的问题如microsoft/TypeScript#30581中所述,我们将其称为“相关联合”。
The first step is to come up with a type that maps your event
values to the parameters list (the args
array).第一步是想出一种将
event
值映射到参数列表( args
数组)的类型。 It's like your TrackablePlayerEventMap
type, but the properties are just parameter tuple types and not function types:它就像您的
TrackablePlayerEventMap
类型,但属性只是参数元组类型而不是函数类型:
interface TPEventParamsMap {
'play:other': ['audio' | 'video'],
error: [Error, Record<string, unknown>];
}
Then we give eventHandlers
type a type written explicitly in terms of TPEventParamsMap
:然后我们给
eventHandlers
类型一个根据TPEventParamsMap
显式编写的类型:
const eventHandlers: {
[K in keyof TPEventParamsMap]?: CallbackFunc<TPEventParamsMap[K]>
} = {};
Conceptually that's the exact same as your Partial<TrackablePlayerEventMap>
type, but this one is explicitly written as a mapped type on TPEventParamsMap
, which the compiler can use to keep track of the correlation between the event
type (of type K
) and the parameters to the event handler function (of type TPEventParamsMap[K]
).从概念上讲,这与您的
Partial<TrackablePlayerEventMap>
类型完全相同,但是这个类型被明确写为TPEventParamsMap
上的映射类型,编译器可以使用它来跟踪event
类型(类型K
)和参数之间的相关性事件处理函数(类型为TPEventParamsMap[K]
)。
Continuing:继续:
function emit<K extends keyof TPEventParamsMap>(
event: K,
...args: TPEventParamsMap[K]
) {
const handler = eventHandlers[event];
handler?.(...args); // okay
}
That compiles with no error.编译没有错误。 Note that since
eventHandlers
is Partial
it might not have a value at key event
, so we are using the optional chaining operator ( ?.
) to call the function only if it exists.请注意,由于
eventHandlers
是Partial
,因此它可能在 key event
处没有值,因此我们使用可选的链接运算符 ( ?.
)仅在该函数存在时才调用该函数。
And emit()
behaves as desired from the caller's side too:并且从调用方的角度来看,
emit()
的行为也符合要求:
emit('error', new Error('Ouch'), { count: 5 }); // okay
emit('error', 123, 456); // error, 123 is not an Error
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.