TypeScript泛型-回调函数推理



我试图理解为什么当泛型是回调函数的返回类型时,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属性之前将objT扩展到{a: any}。所以a1any型。如果编译器推迟了扩展,则可以将此属性表示为查找类型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

游乐场链接到代码

最新更新