在TypeScript中如何使用派生Parameters类型的泛型参数调用函数?
问题描述
当从类型为Parameters<n>的变量中展开参数调用函数n时,运行逻辑完全符合预期。但增加一层间接调用后就会出现问题,示例如下:
type EventSchema = { 'performTask': (task: string, data: number) => void }; // 把 EventSchema 的键映射为对应的监听方法数组 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); // 报错:A spread argument must either have a tuple type or be passed to a rest parameter. }); }
如果将...args的类型显式指定为(task: string, data: number) => void的参数类型,则不会抛出错误。看起来编译器已经能正确推导其类型为元组,为什么还会出现该报错?
报错原因
这是 TypeScript 类型系统当前对未落地到具体值的泛型关联推导的限制导致的。
你定义的emitGlobalEvent是泛型函数,泛型参数K仅被约束为keyof EventSchema,在函数内部没有被窄化为具体字面量类型的前提下,TS 不会自动关联listener的类型EventSchema[K]和args的类型Parameters<EventSchema[K]>的对应关系。
类型检查器会把EventSchema[K]识别为EventSchema所有值的联合类型,把Parameters<EventSchema[K]>识别为所有参数元组的联合类型,无法证明两者是一一匹配的,因此判定 spread 操作不符合类型要求。
当你手动把args指定为[string, number]的固定元组类型时,TS 能确认参数和函数参数匹配,就不会报错。
解决方案
有两种常用的无运行时成本的修复方案:
方案1:对参数做关联类型断言
直接告诉类型检查器args完全匹配当前listener的参数要求:
export function emitGlobalEvent<K extends keyof EventSchema>(event: K, ...args: Parameters<EventSchema[K]>) { listeners[event].forEach((listener) => { listener(...args as Parameters<typeof listener>); }); }
方案2:提前转换事件监听数组的类型
在遍历前就把监听数组的类型和当前args绑定,避免在循环里重复断言:
export function emitGlobalEvent<K extends keyof EventSchema>(event: K, ...args: Parameters<EventSchema[K]>) { const typedListeners = listeners[event] as Array<(...args: typeof args) => void>; typedListeners.forEach((listener) => { listener(...args); }); }
内容的提问来源于stack exchange,提问作者whisperdoll
相关产品推荐
相关产品推荐

