Typescript 接口中带有可选项目的固定长度数组

fixed-length array with optional items in Typescript interface

Angular 通过其 FormBuilder class 引入了模型驱动的表单,其主要方法 group 具有如下签名:

group(controlsConfig: {
        [key: string]: any;
    }): FormGroup;

any实际上是一个数组,格式为:

[
    initial value of model's property, 
    sync validator(s), 
    async validator(s)
]

其中只需要第一个元素。

我决定我想要比这更强类型的东西,特别是与强类型模型相关的任何东西,所以我根据 T 重新定义函数:

declare interface FormBuilder2 extends FormBuilder {
    group<T>(controlsConfig: {
        [K in keyof T]?: [T[K], ValidatorFn | ValidatorFn[] | null, ValidatorFn | ValidatorFn[] | null];
    }): FormGroup;
}

这也意味着我在 HTML 中的所有 formControlName(当然还有 group() 调用中的此处)必须匹配模型的属性,这是我更喜欢的。

这似乎有效,但有一个问题:

    this.optionsForm = this.formBuilder2.group<CustomerModel>({
        status:    [this.model.status, [Validators.required], null],
        lastOrder: [this.model.lastOrder, null, null],
        comments:  [this.model.comments, null, null],
    });

必须在未使用的阵列槽上提供null

有没有办法让 Typescript 忽略对无关 nulls 的需要?

由于元组可以接受额外元素的方式,因此对于元组类型没有真正类型安全的方法。也就是说,例如,元组类型 [A, B, C] 实际上会接受类型 A | B | C (see docs).

的附加元素

不过,还是有办法的! (见下面的尝试 3)

(顺便说一句,你忽略了 Angular 有一个不同的异步验证器接口:AsyncValidatorFn。)

尝试 1:

[K in keyof T]?: [T[K] | ValidatorFn | ValidatorFn[] | null];

几乎不比 any 打字好(可能更糟,因为它看起来具有误导性)。

尝试 2:

[K in keyof T]?:
  [T[K]] |
  [T[K], ValidatorFn | ValidatorFn[]] |
  [T[K], ValidatorFn | ValidatorFn[] | null, AsyncValidatorFn | AsyncValidatorFn[]];

乍一看似乎更好。但问题是 Typescript 编译器只会在万不得已时抛出错误。所以它会接受这个:

someStringField: ['hi', 'hello']

因为这符合[T[K]](因为Typescript中的元组允许有额外的元素)。

尝试 3:

有一个更好的解决方案,令我惊讶的是。我在写这个答案的过程中发现了这一点,同时阅读了 Typescript GitHub repo 上的 this issue

[K in keyof T]?: {
  0: T[K];
  1?: ValidatorFn | ValidatorFn[];
  2?: AsyncValidatorFn | AsyncValidatorFn[];
};

这是对前三个元素的改进,前三个元素总是被正确地类型检查。 ['hi', 'hello'] 给出编译错误,正确。根据通常的结构类型,附加元素是允许的,可以是任何东西,但没关系。

希望这能解决您的问题。