在向交集类型添加属性时,我遇到一个奇怪的问题,通用参数丢失。考虑到以下几点:
type Service = Record<string, any>;
type Dependencies = Record<string, Service>;
type Parameters = Record<string, any>;
type HandlerFunction<D, P, R> = (d: D, p: P) => R;
type ServiceInitializer<D extends Dependencies, S = any> = {
type: string; // ----<HERE>----
} & ((dependencies?: D) => Promise<S>);
function handler<D extends Dependencies, P extends Parameters, R>(
handlerFunction: HandlerFunction<D, P, R>,
name?: string,
dependencies?: string[],
): ServiceInitializer<D, Promise<(p: P) => R>>;
const x = handler(<T>(d: any, { a }: { a: T }): T => a);
如果我删除标有
----<HERE>----
标志的行,我最终会得到以下 x
const 类型,它保留了泛型类型:
const x: <T>(dependencies?: any) => Promise<Promise<(p: {
a: T;
}) => T>>
但是如果我离开这条线,我就会松开它,
T
被unkown
取代:
const x: ServiceInitializer<Record<string, Record<string, any>>, Promise<(p: Record<string, any>) => unknown>>
有没有办法通过仍然允许混合函数和对象属性来保留泛型类型?
这只是 TypeScript 3.4 中引入的对“泛型函数的高阶类型推断”支持的限制,由 microsoft/TypeScript#30215 实现。 在 TypeScript 3.4 之前,编译器在通过指定类型参数及其约束来推断其参数或返回类型时,总是会丢失泛型函数类型。 因此,类型为 <T>(x: T)=>T
的函数将“折叠”为 (x: unknown)=>unknown
,从而产生
[unknown]
的参数元组和 unknown
的返回类型。TypeScript 3.4 添加了支持,因此“有时”可以保留泛型类型参数,但这仅发生在非常特殊的情况下:
当函数调用中的参数表达式属于泛型函数类型时,该函数类型的类型参数将传播到调用的结果类型,如果:被调用的函数是一个泛型函数,它返回带有
单调用签名,此文件的函数类型
单个调用签名本身并不引入类型参数,并且如果您检查代码(
- 在函数调用参数的从左到右处理中,没有对参数表达式的上下文类型中引用的任何类型参数进行推断。
- 就您而言,您显然与第一个(粗体)要点发生了冲突。 仅当被调用函数 (
handler()
- ) 具有返回类型(其本身是具有“单一调用签名”的函数类型)时,才会保留类型参数。
的第 20041 行附近,该文件太大而无法直接链接到 GitHub),您会发现所谓的“单一调用签名”具有相当的限制性:
// If type has a single call signature and no other members, return that signature.
// Otherwise, return undefined.
function getSingleCallSignature(type: Type): Signature | undefined { ... }
看到了吗? “没有其他成员”。由于 handler()
的返回类型是
ServiceInitializer<D, Promise<(p: P) => R>>
,因此在未添加
type
成员的版本中,它仅算作“单个调用签名”。 一旦你添加了它,它就不再是“单一调用签名”,并且你会得到 3.4 之前的行为。
为了让不使用您的特定代码的人清楚地了解,最小示例如下所示:declare function f<A extends any[], R>(f: (...a: A) => R): ((...a: A) => R);
const g = f(<T>(x: T) => x) // const g: <T>(x: T) => T
declare function h<A extends any[], R>(f: (...a: A) => R): ((...a: A) => R) & { x: 0 };
const i = h(<T>(x: T) => x) // const i: ((x: any) => any) & { x: 0; }
函数f()
返回一个“单一调用签名”,因此传递给它的任何通用函数在返回时都保持通用。但是函数
h()
返回一个带有额外成员的函数,因此传递给它的任何泛型函数在返回时都将变为非泛型函数。
对高阶类型推理的支持相当脆弱,因此目前这是不可能的。
Playground 代码链接