[英]How to define a TypeScript type with a variable number of generics
我正在尝试制作一个可以接受多个参数的emit
函数。 此外,TypeScript 将根据第一个参数(事件)验证第二个参数及以后的参数。
我将下面的代码作为起点,但它显然不能按预期工作。
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);
为了在emit()
的主体内编写handler(...args)
并让它编译时没有错误,您需要利用microsoft/TypeScript#47109中引入的技术来解决我的问题如microsoft/TypeScript#30581中所述,我们将其称为“相关联合”。
第一步是想出一种将event
值映射到参数列表( args
数组)的类型。 它就像您的TrackablePlayerEventMap
类型,但属性只是参数元组类型而不是函数类型:
interface TPEventParamsMap {
'play:other': ['audio' | 'video'],
error: [Error, Record<string, unknown>];
}
然后我们给eventHandlers
类型一个根据TPEventParamsMap
显式编写的类型:
const eventHandlers: {
[K in keyof TPEventParamsMap]?: CallbackFunc<TPEventParamsMap[K]>
} = {};
从概念上讲,这与您的Partial<TrackablePlayerEventMap>
类型完全相同,但是这个类型被明确写为TPEventParamsMap
上的映射类型,编译器可以使用它来跟踪event
类型(类型K
)和参数之间的相关性事件处理函数(类型为TPEventParamsMap[K]
)。
继续:
function emit<K extends keyof TPEventParamsMap>(
event: K,
...args: TPEventParamsMap[K]
) {
const handler = eventHandlers[event];
handler?.(...args); // okay
}
编译没有错误。 请注意,由于eventHandlers
是Partial
,因此它可能在 key event
处没有值,因此我们使用可选的链接运算符 ( ?.
)仅在该函数存在时才调用该函数。
并且从调用方的角度来看, 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.