我试图理解为什么当泛型是回调函数的返回类型时,Typescript能够用回调参数推断函数的返回型,而当泛型是回叫函数的类型时却不能这样做。
没有具体的推论
const f = <T extends () => any>(callback: T) => callback()
// inference r: any
const r = f(() => 1)
但是这个推论适用于
const f = <T extends any>(callback: () => T) => callback()
// inference r: number
const r = f(() => 1)
我认为这是编译器在对泛型类型执行某些操作时所做的简化。它没有将每个操作表示为可能越来越复杂的泛型类型,而是将泛型类型参数扩展到其约束并使用它。当你用一个已知的键索引到一个泛型类型的对象时,你可以看到这种情况:
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。在上文中,编译器看到obj.a
,并在访问其a
属性之前将obj
从T
扩展到{a: any}
。所以a1
是any
型。如果编译器推迟了扩展,则可以将此属性表示为查找类型T['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>
(请参阅ReturnType
的文档(。同样,如果手动将值注释为ReturnType<T>
,编译器不会抱怨它
这让我找到了我认为适合您的解决方案/变通方法:手动注释函数的返回类型:
const f = <T extends () => any>(callback: T): ReturnType<T> => callback()
它编译时没有错误,现在当你在回调中调用f
时,你会得到一个更好的返回类型:
const r = f(() => 1); // number
游乐场链接到代码