【问题标题】:Infer Correct Return Type of Subclasses for Method推断方法子类的正确返回类型
【发布时间】:2020-04-22 11:58:46
【问题描述】:

我有以下类层次结构

TypeScript Playground

export abstract class AbstractControlModel<T> { ... }

export class FormArrayModel<T = any> extends AbstractControlModel<T[]> {
  constructor(private readonly _controls: AbstractControlModel<T>[] = []) { ... }

  findControl<E extends AbstractControlModel<T> = FormControlModel<T>>(index: number): E {
    if (!(index in this._controls))
      throw new Error(`Index "${index}" does not exist in this form array `);
    return this._controls[index] as E;
  }
}

export class FormGroupModel<T> extends AbstractControlModel<T>
  constructor(private _controls?: { [E in keyof T]: AbstractControlModel<T[E]> }) { ... }

  findControl<K extends keyof T>(name: K) {
    return this.controls.get(name);
  }

我创建了一个FormGroupModel 对象

const formGroup = new FormGroupModel({
    fullName: new FormControlModel('Hello World'), // Inferred type is FormControlModel<string>,
    addresses: new FormArrayModel([ // Inferred type is FormArrayModel<{street: string; city: string;}>
        new FormGroupModel({ // Inferred type is FormGroupModel<{street: string; city: string}>
            street: new FormControlModel('123 Street'),
            city: new FormControlModel('City')
        })
    ])
});

const fullNameControl = formGroup.findControl('fullName'); // The returned type is AbstractControlModel<any> instead of FormControlModel<string>

当我在上面的formGroup 变量上调用findControl 时,我希望TypeScript 返回一个FormControl&lt;string&gt; 类型的对象,而不是AbstractControl&lt;any&gt;。我应该如何更改声明以实现这一目标。非常感谢任何帮助。

【问题讨论】:

  • 您能否将那些... 替换为可以编译并演示您的问题的内容?大概您需要FormGroup&lt;T&gt; 来跟踪T 的每个属性的控件类型,但是如果没有针对类层次结构的一些更具体的声明,我不确定如何最好地提出建议。除非您希望其他人编造一些东西,否则您不太可能对答案感到满意。祝你好运!
  • 好吧,我编造了一些东西!希望它能满足您的需求!
  • @jcalz 我为他们添加了完整的类定义
  • 理想情况下,minimal reproducible example 的代码足以说明问题,最好是可以放入独立 IDE 中的代码,例如 The Playground
  • @jcalz 添加了一个工作游乐场的链接

标签: typescript type-inference typescript-generics


【解决方案1】:

正如目前所写,controls 的类型具体定义为 {[K in keyof T]: AbstractControl&lt;T[K]&gt;},这意味着获取该类型上的键 K 不会为您提供所使用的实际值/类的准确类型信息,但是抽象类型,所以你得到的是anyunknown

相反,您应该将controls 的类型设为泛型,从该类型扩展,如下所示:

class FormGroup<T, C extends {[K in keyof T]: AbstractControl<T[K]>}> extends AbstractControl<T> {
    constructor(private controls: C) { ... }

    findControl<K extends keyof C>(name: K) {
        return this.controls[name];
    }
}

注意controls 的类型是新的泛型CfindControl 需要C 的键,而不是T。这应该允许 TypeScript 正确确定返回类型:

const formGroup = new FormGroup({
    fullName: new FormControl('Hello World'),
    addresses: new FormArray([
        new FormGroup({
            street: new FormControl('123 Street'),
            city: new FormControl('City')
        })
    ])
})

// Correctly inferred as FormControl<string>
const fullNameControl = formGroup.findControl('fullName')

Playground Link,将...s 替换为super(),这样类型检查器就不会报错。

【讨论】:

  • 我喜欢这个答案,但我不确定T 是否在做任何事情,因为它仍被推断为unknown;你可能想摆脱它,只保留C
  • 我相信T 仍然是必要的,因为您需要TC 的通用“内部” - 可以不直接将 T 指定为外部的类型C的定义,现在还在用。
  • T 在您的示例代码中被推断为unknown,所以它真的没有做任何事情。将T 删除为泛型并将所有其他T 替换为unknown,您将获得相同的行为。此外,这意味着您认为您正在设置的约束没有被设置;就目前而言,您的代码会验证 new FormGroup("somethingClearlyNotAcceptable") 之类的内容,因为 C 不受任何限制。
  • 是的,FormGroup 的值的类型被推断为unknown
【解决方案2】:

我建议将通用 T 设为 controls 的类型,并从中计算“普通”版本,反之亦然。像这样的:

class FormGroup<T extends Record<keyof T, AbstractControl<any>>> extends
  AbstractControl<{ [K in keyof T]: T[K] extends AbstractControl<infer V> ? V : never }> {

  constructor(private controls: T) { super() }

  findControl<K extends keyof T>(name: K) {
    return this.controls[name];
  }
}

这里,普通版本是{[K in keyof T]: T[K] extends AbstractControl&lt;infer V&gt; ? V : never},意思是你遍历每个控件,看看它代表什么类型。

您也可以对FormArray 做类似的事情:

class FormArray<T extends AbstractControl<any>[]> extends
  AbstractControl<T[number] extends AbstractControl<infer V> ? V[] : never> {
  constructor(private controls: T) { super() }

  findControl<N extends number>(index: N) {
    return this.controls[index];
  }
}

现在您的示例如下所示:

const formGroup = new FormGroup({
  fullName: new FormControl('Hello World'),
  addresses: new FormArray([
    new FormGroup({
      street: new FormControl('123 Street'),
      city: new FormControl('City')
    })
  ])
});

const fullNameControl = formGroup.findControl('fullName'); // FormControl<string>

甚至

const cityControl = formGroup.findControl('addresses').findControl(0)
  .findControl("city"); // FormControl<string>

好的,希望对您有所帮助;祝你好运!

Link to code

【讨论】:

  • 这不会使 3 个抽象方法的子类实现通过,因为由于某种原因,抽象方法返回/期望与具体实现不同的类型,但这看起来很有希望
猜你喜欢
  • 2011-01-13
  • 2012-08-15
  • 2021-06-11
  • 2016-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多