在多个级别上推断 TypeScript 中的泛型类型参数

Infer generic type parameters in TypeScript over multiple levels

我的 TypeScript 代码中有以下常见场景:

interface TopLevelInterface<A extends BottomLevelInterface<B>, B>

BottomLevelInterface 的实现如下所示:

class BottomLevelClass implements BottomLevelInterface<MyType>

我的问题是:在实现 TopLevelInterface 时,我不仅需要传递 A 的类型参数,即 BottomLevelClass,还需要传递 [=19] 的第二个类型参数=] 在上例中为 MyType

为什么我需要指定 B 这可以通过查看 BottomLevelClass 的类型参数轻松推断出来?

例如,在实现TopLevelInterface时,我需要指定以下内容:

class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel, MyType>

而不是应该足够的较短版本:

class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel>

为什么这是必要的?如何通过查看第一个参数来推断第二个类型参数?我想出的唯一解决方案是在 TypeScript 2.8+ 中使用 infer 和默认分配。此解决方案如下所示:

interface TopLevelInterface<A extends BottomLevelInterface<B>, 
    B = A extends BottomLevelInterface<infer _B> ? _B : any>

但是,我无法将其正确应用于三层 class 层次结构。在下面的 StackBlitz 中,您可以看到我无法获得 TopLevelClass 的 属性 model 的正确类型约束。它应该被推断为 SomeType 类型,但被推断为 never。在 MiddleLevelClass 它确实可以正常工作。

https://stackblitz.com/edit/typescript-pcxnzo?file=infer-generics.ts

任何人都可以解释这个问题或实现预期结果的更好方法吗?

MiddleLevelClass 中的蓄意错误正在影响 TopLevelClass 的行为,因此对于有效的测试,我们应该 TopLevelClass 基于正确的 MiddleLevelClass 并使用单独 BadMiddleLevelClass 以证明那里的错误。

您的第一个问题是您的条件类型有一个 "else" 大小写 any,这将倾向于隐藏错误。 never 往往更好并且被广泛使用,尽管完整的解决方案需要 a unique invalid type.

有了这些更改,主要问题似乎是当您编写 TopLevelInterface<MiddleLevelClass> 并且 TypeScript 尝试计算 B = M extends MiddleLevelInterface<infer _B> ? _B : never 时,_B 没有推论,因为 B 参数实际上并未被 MiddleLevelInterfaceMiddleLevelClass 使用。参见 this FAQ。添加一个使用 B 的虚拟可选 属性 可以解决问题。 (我猜你在实际应用程序中使用了 B,否则你不会声明 B,但你在简化示例中删除了使用?)

新代码:

export class SomeType {
  x: string;
}

export interface BottomLevelInterface<T> {
  model : T;
}

export class BottomLevelClass implements BottomLevelInterface<SomeType> {
  model: SomeType;
}

export interface MiddleLevelInterface<B extends BottomLevelInterface<T>, 
    T = B extends BottomLevelInterface<infer _T> ? _T : never> {
  _dummy_B?: B;

  model: T;
} 

export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  _dummy_B?: BottomLevelClass;
  model: SomeType;
}

export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  // here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
  model: string;
}

export interface TopLevelInterface <M extends MiddleLevelInterface<B, T>,
    B extends BottomLevelInterface<T> = M extends MiddleLevelInterface<infer _B> ? _B : never,
    T = B extends BottomLevelInterface<infer _T> ? _T : never> {

  model: T;
}

export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
  // now there is an error here
  model: string;
}

基于 jcalz 的建议(感谢!)的原始问题的替代解决方案:不使用多个类型参数,而是使用辅助类型别名从 B 类型确定 T 类型和每次需要时,从 M 键入 B。这是代码:

export class SomeType {
  x: string;
}

export interface BottomLevelInterface<T> {
  model : T;
}

export class BottomLevelClass implements BottomLevelInterface<SomeType> {
  model: SomeType;
}

type TfromB<B extends BottomLevelInterface<any>> = B extends BottomLevelInterface<infer T> ? T : never;

export interface MiddleLevelInterface<B extends BottomLevelInterface<any>> {
  _dummy_B?: B;

  model: TfromB<B>;
} 

export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  _dummy_B?: BottomLevelClass;
  model: SomeType;
}

export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  // here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
  model: string;
}

type BfromM<M extends MiddleLevelInterface<any>> = M extends MiddleLevelInterface<infer B> ? B : never;

export interface TopLevelInterface <M extends MiddleLevelInterface<any>> {

  model: TfromB<BfromM<M>>;
}

export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
  // now there is an error here
  model: string;
}

如果对条件类型而不是显式类型参数的依赖使超出这个简单示例的某些操作变得更加困难,我不会感到惊讶。你可以试试看。