如何为泛型函数类型定义泛型实例箭头函数

问题描述 投票:0回答:1

我需要为函数签名定义一种类型,以便在多个函数定义中使用。该类型必须是通用的,并接收一个通用类型,该类型将用于将函数结果转换为它。

工作示例:

async function callEndpoint1<ReturnT>(
  url: string,
  init?: RequestInit
  ):Promise<ReturnT> {
 // assume the object string is return from an http request
   return JSON.parse("{}") as ReturnT;
}

async function callEndpoint2<ReturnT>(
  url: string,
  init?: RequestInit
  ):Promise<ReturnT> {
 // assume the object string is return from an http request
   return JSON.parse("{}") as ReturnT;
}

const x1 = callEndpoint1<{prop:string}>("a-url");
const x2 = callEndpoint2<{prop:string}>("a-url");

这就是我想要实现的目标,但我做不到:

type CallEndpointFunction = <ReturnT>(
    url: string,
    init?: RequestInit
) => Promise<ReturnT>;

const callEndpoint3: CallEndpointFunction = async (url, init) => {
    return JSON.parse("{}") as ReturnT; // error!
    //                         ~~~~~~~
    // Cannot find name 'ReturnT'.
}
const x3 = callEndpoint3<{ prop: string }>("a-url");

更新 虽然我仍然很想知道我在 TS 中试图实现的目标是否有答案,但我不会使用答案,因为函数接收泛型类型参数似乎不是最佳实践盲目地将结果投射到。

typescript function generics
1个回答
0
投票

请参阅 microsoft/TypeScript#40287 获取权威答案。

如果您想在泛型函数实现中访问generic类型参数名称,则需要显式声明泛型类型参数,并显式注释函数参数

const callEndpoint: CallEndpointFunction =
    async <ReturnT,>(url: string, init?: RequestInit) => {
        return JSON.parse("{}") as ReturnT
    };

注意类型参数和函数参数的名称是任意的;它们不需要与

CallEndpointFunction
类型定义中的名称相同。这和上面是一样的:

const callEndpoint: CallEndpointFunction =
    async <X,>(u: string, i?: RequestInit) => {
        return JSON.parse("{}") as X
    };

如果您决定不注释

url
init
函数参数,因为您想使用 上下文打字 让 TypeScript 为您推断它们的类型,那么不幸的是您仍然无法声明泛型类型参数。如果您声明类型参数,则上下文类型不会发生,并且参数类型隐式回退到
any

const callEndpoint: CallEndpointFunction =
    async <ReturnT,>(url, init) => { // error!
        //           ~~~  ~~~~
        // Parameter implicitly has an 'any' type.
        return JSON.parse("{}") as ReturnT
    };

我可以想象有人会提出一个功能请求,要求与上下文打字一起使用,但我怀疑它会有很大的社区需求。

另一方面,如果您省略泛型类型参数声明,上下文类型就可以工作,并且该函数仍然是泛型的。但现在类型参数是anonymous;正如您所看到的,您无法通过名称来引用它:

const callEndpoint: CallEndpointFunction = async (url, init) => {
    return JSON.parse("{}") as ReturnT; // error!
    //                         ~~~~~~~
    // Cannot find name 'ReturnT'.
}

因此,如果您想这样做,您应该显式注释函数调用签名。 对于您的特定示例,这并不重要,因为

JSON.parse()
在 TypeScript 中返回
any
,并且
any
可分配给匿名类型参数:

const callEndpoint7: CallEndpointFunction =
    async (url, init) => {
        return JSON.parse("{}"); // okay because any
    }

此时,您或多或少已经别无选择。类型参数仅在函数的返回类型中提及,因此在函数实现中没有任何值,其类型可用于恢复匿名类型参数。 像这样只引用一次类型参数不太好;它违反了泛型的黄金法则,即类型参数应该在至少两个地方提及,在这种情况下,函数只是声称能够返回与任何函数参数无关的某种类型的值。这是不可能安全地完成的(

callEndpoint<string>("")
callEndpoint<number>("")
都编译为
callEndpoint("")
,并且大概实际函数不可能在一种情况下返回
string
,而在另一种情况下返回
number
。)

如果函数多次引用类型参数,如

type SomeOtherFunc = <ReturnT>(
    x: ReturnT[] // <-- reference here
) => Promise<ReturnT>; // <-- reference here

然后您可以使用上下文类型并可以访问类型包含匿名类型参数的值:

const func: SomeOtherFunc = async (x) => {
    const ret = x[0];
    //    ^? const ret: ReturnT (this type is anonymous)
    return ret;
}

这是实现具有匿名泛型类型的函数的最常见方式,根本不需要显式提及类型(这就是为什么我怀疑上述功能请求会受欢迎)。但是,如果需要,您可以使用该参数为匿名类型参数命名:

const func: SomeOtherFunc = async (x) => {
    type MyReturnT = typeof x[number];
    // type MyReturnT = ReturnT (anonymous)
    return JSON.parse("{}") as MyReturnT; // okay
}

对于您的问题来说,这是一个合理的解决方法,但同样,只有在可以从函数参数类型恢复类型参数的黄金法则情况下。 您的代码违反了该规则(这就是为什么,正如您所说,这不一定是最佳实践),因此如果您实际上需要引用泛型类型参数名称,您唯一的选择是手动注释所有内容。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.