我认为这是编译器在对泛型类型执行某些操作时所做的简化。它没有将每个操作表示为可能越来越复杂的泛型类型,而是将泛型类型参数扩展到其约束并使用它。当您使用已知的键对泛型类型对象进行索引时,您会看到这种情况:
function foo<T extends { a: any }>(obj: T) {
const a1 = obj.a; // any, why not T['a']?
const a2: T['a'] = obj.a; // this works though
}
请参阅microsoft/TypeScript#33181 了解更多信息。在上面,编译器在访问其a 属性之前看到obj.a 并将obj 从T 扩大到{a: any}。所以a1 的类型是any。如果编译器推迟了扩展,它可以将此属性表示为lookup typeT['a']。实际上,如果您将要保存的变量显式注释为T['a'],编译器不会抱怨。
调用泛型类型的函数似乎也会发生同样的情况(尽管我还没有找到提到这一点的规范文档):
function bar<T extends () => any>(fn: T) {
const r1 = fn(); // any, why not ReturnType<T> ?
const r2: ReturnType<T> = fn(); // this works though
}
如您所见,r1 的类型为 any,因为编译器在调用之前将 fn 从 T 扩大到其约束 () => any。如果编译器推迟了扩展,它可以将返回类型表示为ReturnType<T>(请参阅documentation for ReturnType)。同样,如果您手动将值注释为ReturnType<T>,编译器不会抱怨它。
这使我找到了我认为适合您的解决方案/解决方法:手动注释函数的返回类型:
const f = <T extends () => any>(callback: T): ReturnType<T> => callback()
编译时没有错误,现在当您在回调中调用 f 时,您会获得更好的返回类型:
const r = f(() => 1); // number
Playground link to code