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

ggr*_*nig 5 generics type-inference typescript

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

interface TopLevelInterface<A extends BottomLevelInterface<B>, B>
Run Code Online (Sandbox Code Playgroud)

其实现BottomLevelInterface如下所示:

class BottomLevelClass implements BottomLevelInterface<MyType>
Run Code Online (Sandbox Code Playgroud)

我的问题是:在实现时,TopLevelInterface我不仅需要传递 的类型参数ABottomLevelClass还需要传递B上面MyType示例中的第二个类型参数。

为什么我需要指定B通过查看 的类型参数可以轻松推断出哪些内容BottomLevelClass

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

class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel, MyType>
Run Code Online (Sandbox Code Playgroud)

而不是更短的版本就足够了:

class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel>
Run Code Online (Sandbox Code Playgroud)

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

interface TopLevelInterface<A extends BottomLevelInterface<B>, 
    B = A extends BottomLevelInterface<infer _B> ? _B : any>
Run Code Online (Sandbox Code Playgroud)

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

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

谁能解释这个问题或更好的方法来达到预期的结果?

Mat*_*hen 3

中的故意错误MiddleLevelClass正在影响 的行为TopLevelClass,因此对于有效的测试,我们应该TopLevelClass在正确的基础上MiddleLevelClass并使用单独的BadMiddleLevelClass错误来演示那里的错误。

您的第一个问题是您的条件类型有一个“else”情况any,这往往会隐藏错误。 尽管完整的解决方案需要独特的类型never,但往往更好并且常用。invalid

通过这些更改,似乎主要问题是,当您编写TopLevelInterface<MiddleLevelClass>并且 TypeScript 尝试评估时B = M extends MiddleLevelInterface<infer _B> ? _B : never,不会进行任何推断,_B因为该B参数实际上并未被MiddleLevelInterfaceor使用MiddleLevelClass。请参阅此常见问题解答。添加一个使用的虚拟可选属性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;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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