如何在 TypeScript 中使用具有派生参数类型的通用 args 变量调用函数?



当调用一个函数n参数从 Parameters<n>类型的变量中传播时,一切似乎都如您所料井井有条。但似乎添加一层间接性会给事情带来麻烦,例如

type EventSchema = {
'performTask': (task: string, data: number) => void
};  
// Mapping keys of EventSchema to arrays of listener methods
const listeners: {[p in keyof EventSchema]: EventSchema[p][]} = {
'performTask': []
};
export function emitGlobalEvent<K extends keyof EventSchema>(event: K, ...args: Parameters<EventSchema[K]>) {
listeners[event].forEach((listener) => {
listener(...args); // Error: A spread argument must either have a tuple type or be passed to a rest parameter.
});
}

...args类型更改为显式(task: string, data: number) => void时,不会引发任何错误。似乎编译器能够正确推断类型,并且它是一个元组,那么为什么会出现此错误?

为了向 TS 保证将...args传递给listener是安全的,我认为值得为您的函数提供listeners参数:

type EventSchema = {
'performTask': (task: string, data: number) => void
};
// Mapping keys of EventSchema to arrays of listener methods
const listeners: { [p in keyof EventSchema]: EventSchema[p][] } = {
'performTask': []
};

export function emitGlobalEvent<
K extends keyof EventSchema,
Listeners extends Record<K, Array<(...args: Parameters<EventSchema[K]>) => void>>
>(listeners: Listeners, event: K, ...args: Parameters<EventSchema[K]>) {
listeners[event].forEach((listener) => {
listener(...args); // no error
});
}
emitGlobalEvent(listeners, 'performTask', 'task', 42)

操场

不要忘记您可以curry它:

type EventSchema = {
'performTask': (task: string, data: number) => void
};
// Mapping keys of EventSchema to arrays of listener methods
const listeners: { [p in keyof EventSchema]: EventSchema[p][] } = {
'performTask': []
};
const withListeners = <
K extends keyof EventSchema,
Listeners extends Record<K, Array<(...args: Parameters<EventSchema[K]>) => void>>
>(listeners: Listeners,) =>
(event: K, ...args: Parameters<EventSchema[K]>) =>
listeners[event].forEach((listener) => listener(...args));
const emitGlobalEvent = withListeners(listeners)
emitGlobalEvent('performTask', 'task', 42)

操场

最新更新