泛型 const 可以调用另一个泛型吗?

Can a generic const call another generic?

考虑以下使用泛型的 Typescript:

//Assume we've got data coming from somewhere we cannot control the type
const fetchFromSomewhere: (str: any) => any = (str: any) => str

const fetchOne: <T>(str: string) => T = (str: string) => {
    const a = fetchFromSomewhere(str)
    return a
}

//Does not work
const fetchTwo: <T>(str: string) => T = (str: string) => {
    const a = fetchOne<T>(str) //Cannot find name 'T'.ts(2304)
    return a
}

//Works
function fetchThree<T>(str: string): T {
    const a = fetchOne<T>(str) //Can use <T> here
    return a
}

fetchTwo 编译失败,说 Cannot find name 'T'.ts(2304)。省略 <T> 会导致其 return 类型为 unknown

fetchThree 工作正常,是一个合适的解决方法。但是有没有一种方法可以使用 const 而不是函数来实现 fetchTwo

如果你想 annotate the call signature of an arrow function expression in TypeScript so that it is generic,你可以通过将类型参数声明紧接在参数列表之前来实现(并且这个列表必须放在括号中,即使只有其中一个)。例如,您可以转

const idArrow = x => x;

进入

const idArrow = <T>(x: T) => x;

类似于以下函数语句:

function idStatement<T>(x: T) { return x };

请注意,如果您正在使用 JSX 或您的 IDE 需要 JSX,则上述箭头函数可能会混淆它。毕竟,JSX 表达式看起来像 HTML 标签,<T> 看起来也像。为了防止这种混淆,您可以在类型参数后添加尾随逗号(类型参数列表允许尾随逗号):

const idArrow = <T,>(x: T) => x; 

这完全等同于 idArrow 的先前定义,只是即使您的编译器设置为允许 JSX,此定义也能正常工作。


无论如何,这意味着您可以这样做:

const fetchOkay = <T,>(str: string) => {
    const a = fetchOne<T>(str)
    return a
}

如果未在调用签名中声明,则不能在箭头函数体内使用 T 参数:

const fetchBad = (str: string) => {
    const a = fetchOne<T>(str) //Cannot find name 'T'.ts(2304)
    return a
}

后一种情况正是您 fetchTwo 中发生的情况。


注意箭头函数表达式只是表达式;它们不必分配给变量。如果您确实将一个分配给变量,那么您可以独立选择 annotate the variable 或不。即可以注解:

const v: Type = init;

如果这样做,变量的类型注释将完全位于赋值运算符 (=) 的左侧,并且初始化表达式不会直接参与其中。

在您的 fetchTwo 声明中,您使用通用函数类型注释 fetchTwo 变量:

const fetchTwo: <T>(str: string) => T = ...;

并且您的初始化值是之前有问题的箭头函数,其中 T 不在范围内。

(str: string) => {
    const a = fetchOne<T>(str) //Cannot find name 'T'.ts(2304)
    return a
}

希望您现在可以看到断开连接。我个人会放弃类型注释,让编译器推断 fetchTwo 变量的类型。但是如果你真的要注解,那就意味着你需要分别对变量和函数表达式的调用签名进行注解:

const fetchTwo: <T>(str: string) => T = <T,>(str: string) => {
    const a = fetchOne<T>(str)
    return a
}

而且那些真的是分开的;您可以重命名函数表达式中的函数参数和类型参数,而不会影响变量类型注释:

const fetchTwo: <T>(str: string) => T = <U,>(xxx: string) => {
    const a = fetchOne<U>(xxx)
    return a
}

Playground link to code