我需要为函数签名定义一种类型,以便在多个函数定义中使用。该类型必须是通用的,并接收一个通用类型,该类型将用于将函数结果转换为它。
工作示例:
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 中试图实现的目标是否有答案,但我不会使用答案,因为函数接收泛型类型参数似乎不是最佳实践盲目地将结果投射到。
请参阅 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
}
对于您的问题来说,这是一个合理的解决方法,但同样,只有在可以从函数参数类型恢复类型参数的黄金法则情况下。 您的代码违反了该规则(这就是为什么,正如您所说,这不一定是最佳实践),因此如果您实际上需要引用泛型类型参数名称,您唯一的选择是手动注释所有内容。