TypeScrip有一个普遍的限制,它不能总是同时推断generic个类型参数和某个表达式的contextual type个,而这些至少在句法上是有可能循环地相互依赖的.TypeScrip的推理算法不使用microsoft/TypeScript#30134中讨论的所谓的"Full unification",因此总是会有一些情况下,它无法推断出您所期望的东西.在这些情况下,您始终可以只指定annotate your callback parameters或手动指定泛型类型参数.
在您的特定示例中,给定调用签名
function run<U>(func1: (arg: U) => void, func2: (arg: { a: string }) => U): void {}
呼唤
run((params) => {}, () => 23);
行为符合预期,因为可以从() => 23
的返回类型推断出U
,而且() => 23
不是context-sensitive.已知它是() => number
类型的,而不必判断在其中发现它的context.回调(params) => {}
是上下文敏感的,其中params
是类型U
.由于已知U
是number
,则推断params
是类型number
.
On the other hand, 呼唤
run((params) => {}, (arg) => 23);
并不像预期的那样工作.同样,U
需要从(arg) => 23
的返回类型推断出来. 但是(arg) => 23
is是上下文敏感的. 它的类型取决于arg
的类型,而arg
的类型取决于表达式所在的上下文.TypeScript决定在这里执行defer. 从这里开始一切都被分解了;唯一可以推断出U
的地方是参数类型(params) => {}
,但这也是上下文敏感的.所以对于U
没有什么可以推断的.推理失败,U
返回到unknown
,params
属于unknown
类型.
当然,现在(arg) => 23
的返回类型不是actually依赖于arg
的类型,因为23
无论如何都是number
.因此,应该可以想象修复它的编译器改进.开放特征请求microsoft/TypeScript#47599是关于循环依赖仅为apparent的情况.
事实上,在类似案件中取得了一些进展. TypeScript 4.7引入了improved function inference in objects and methods,在microsoft/TypeScript#48538中实现:
当在上下文中键入泛型函数参数列表中的箭头函数、函数表达式和对象文字方法的参数时,我们从参数列表and from context sensitive function arguments in preceding positions in the argument list.中的任何位置的context insensitive个函数参数推断类型
这意味着,如果您重新排列函数参数,以便可以从first参数而不是second参数推断出U
,那么您应该能够获得所需的推论.这样,推理就可以从左到右而不是从右到左:
function run2<U>(
func2: (arg: { a: string }) => U,
func1: (arg: U) => void
): void { }
run2(() => 23, (params) => { },);
// ^?(parameter) params: number
run2((arg) => 23, (params) => { },);
// ^?(parameter) params: number
这是可行的,因为即使(arg)=>23
也是(params)=>{}
之前的上下文敏感函数参数.这样的重构并不总是可接受的(例如,您不能更改现有的库),有时不存在可以工作的重构(参见顶部段落),因此它在很大程度上取决于您的用例如何进行.
Playground link to code个