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

标签 typescript generics type-inference

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

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

以及 BottomLevelInterface 的实现看起来像这样:
class BottomLevelClass implements BottomLevelInterface<MyType>

我的问题是:在实现 TopLevelInterface 时我不仅需要为 A 传递类型参数这将是 BottomLevelClass , 也是 B 的第二种类型参数这将是 MyType在上面的例子中。

为什么我需要指定B通过查看 BottomLevelClass 的类型参数可以很容易地推断出这一点。 ?

例如,在实现 TopLevelInterface 时我需要指定以下内容:
class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel, MyType>

而不是应该足够的较短版本:
class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel>

为什么这是必要的?如何通过查看第一个参数来推断第二个类型的参数?我想出的唯一解决方案是使用 infer在带有默认分配的 TypeScript 2.8+ 中。此解决方案如下所示:
interface TopLevelInterface<A extends BottomLevelInterface<B>, 
    B = A extends BottomLevelInterface<infer _B> ? _B : any>

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

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

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

最佳答案

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

您的第一个问题是您的条件类型具有 any 的“其他”案例| ,这将倾向于隐藏错误。 never往往会更好并且被普遍使用,尽管完整的解决方案需要 a unique invalid type .

有了这些变化,似乎主要的问题是当你写 TopLevelInterface<MiddleLevelClass> 时并且 TypeScript 尝试评估 B = M extends MiddleLevelInterface<infer _B> ? _B : never ,没有对 _B 的推论因为B MiddleLevelInterface 实际并未使用参数或 MiddleLevelClass .见 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 建议的原始问题的替代解决方案(谢谢!):不要使用多个类型参数,而是使用辅助类型别名来确定 T输入来自 B类型和 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;
}

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

关于typescript - 在多个级别上推断 TypeScript 中的泛型类型参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52905336/

相关文章:

android - 如何从参数类型为 SomeType<*> 的 kotlin 重写 java 中的方法?但需要分配给 SomeType<String>?

arrays - 无法使用类型为 'append' 的参数列表调用 '(T)'

parsing - 编译器是否足够聪明,可以根据值在需要该类型的函数中的用法来推断已解析值的类型?

rust - 为什么对不明确的数字进行方法调用会导致错误?

Typescript 装饰器重写属性

java - 如何覆盖通用存储库错误消息

reactjs - 在 React 项目上运行 "Invalid option for project: true"时会产生什么 'tslint --project'?

typescript - 如何告诉 TypeScript 两个泛型类型相同?

Java 泛型,以类对象作为泛型类型

java - 目标类型具有通配符时的泛型方法类型推断