简体   繁体   English

如何使用可变数量的泛型定义 TypeScript 类型

[英]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.请注意,由于eventHandlersPartial ,因此它可能在 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

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM