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 忽略对无关 null
s 的需要?
由于元组可以接受额外元素的方式,因此对于元组类型没有真正类型安全的方法。也就是说,例如,元组类型 [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']
给出编译错误,正确。根据通常的结构类型,附加元素是允许的,可以是任何东西,但没关系。
希望这能解决您的问题。
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 忽略对无关 null
s 的需要?
由于元组可以接受额外元素的方式,因此对于元组类型没有真正类型安全的方法。也就是说,例如,元组类型 [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']
给出编译错误,正确。根据通常的结构类型,附加元素是允许的,可以是任何东西,但没关系。
希望这能解决您的问题。