TypeScript中Parameters工具类型以泛型为索引时类型推导异常
请参考对应示例,代码如下:
enum Options { one, two, three } const dictionary = { [Options.one]: (a: string, b: boolean) => { console.log({ a: a, b: b }) }, [Options.two]: (c: number) => { console.log(c) }, [Options.three]: () => { }, } as const function complicated<T extends Options>(option: T, ...args: Parameters<typeof dictionary[T]>) { const fn = dictionary[option] type b = typeof dictionary[T] fn(...args) // TypeScript此处提示类型错误,但代码实际运行无异常 }
当静态声明类型如type One = Parameters<typeof dictionary[Options.one]>时,类型推导结果正确,为[a: string, b: boolean]。
但在上述泛型场景中使用Parameters<typeof dictionary[T]>时,fn变量的类型被推导为(arg0: never, b: boolean) => void,调用fn(...args)会触发TypeScript类型校验错误,但代码实际运行完全正常。
问题产生原因
这是TypeScript泛型关联类型推导的已知设计限制,对应公开已知问题编号#49802,关联issue #49700:
- 当使用泛型
T extends Options时,TypeScript不会对「同个泛型参数索引得到的函数类型」和「同个泛型参数推导得到的Parameters参数元组类型」做关联绑定校验。 - 编译器处理
dictionary[option]时,会将其类型推导为字典所有值的函数类型联合:((a: string, b: boolean) => void) | ((c: number) => void) | (() => void),而非和T绑定的精准对应类型。 - 给联合类型的函数传参时,TypeScript按照函数参数的逆变校验规则,会将参数类型推导为所有函数参数类型的交集,第一个参数的交集为
string & number & void也就是never,因此会出现(arg0: never, b: boolean) => void的错误推导,最终触发类型报错。 - 静态声明类型时推导正确,是因为此时
T是具体的枚举成员字面量类型,不存在泛型未确定的情况,编译器可以直接拿到精准的类型映射,不会触发联合类型逆变推导的问题。
消除类型错误的方案
以下两种方案均无额外运行时开销,不会改变原有代码的运行逻辑:
方案1:类型断言(最简洁,适合普通工具函数场景)
直接明确告知编译器fn的参数类型和传入参数匹配即可:
function complicated<T extends Options>(option: T, ...args: Parameters<typeof dictionary[T]>) { const fn = dictionary[option] as (...args: Parameters<typeof dictionary[T]>) => void fn(...args) // 无类型报错 }
方案2:封装内部调用(无类型断言,适合严格类型校验场景)
通过内部匿名函数绕开泛型关联推导限制,不需要写额外的类型断言:
type Dictionary = typeof dictionary function complicated<T extends Options>(option: T, ...args: Parameters<Dictionary[T]>) { const call = (opt: keyof Dictionary, ...fnArgs: any[]) => dictionary[opt](...fnArgs) call(option, ...args) // 无类型报错 }
内容的提问来源于stack exchange,提问作者David Min
相关产品推荐
相关产品推荐

