我有一个通用类,其中paramater类型是一个元组。我在类上创建一个方法时遇到了麻烦,该方法的参数仅限于元组的索引。
例如(playground link):
class FormArray<T extends [any, ...any[]]> {
constructor(public value: T) {}
// how to restrict `I` to only valid index numbers of `T` ?
get<I extends keyof T>(index: I): T[I] {
return this.value[index];
}
}
我知道您可以做的是使用keyof
获取元组的所有属性,其中将包括与元组包含的对象相关的键(即“ 0”,“ 1”等) )。不幸的是,keyof
在元组中引入了 all 所有属性,包括“ length”,“ splice”等。
我尝试使用keyof
并排除了所有非number
类型的属性,但是后来我意识到索引属性(“ 0”,“ 1”等)由{返回{1}}作为类型keyof
。
目前可以在Typescript中完成此操作吗?谢谢!
要添加到下面接受的答案中,以下是一种解决方法
string
在这里,如果元组的长度为13或更短,我们就可以成功提取出该元组的属性索引号。否则,我们返回通用索引type ArrayKeys = keyof any[];
type StringIndices<T> = Exclude<keyof T, ArrayKeys>;
interface IndexMap {
"0": 0,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"10": 10,
"11": 11,
"12": 12,
}
type CastToNumber<T> = T extends keyof IndexMap ? IndexMap[T] : number;
type Indices<T> = CastToNumber<StringIndices<T>>;
class FormArray<T extends [any, ...any[]]> {
constructor(public value: T) {}
get<I extends Indices<T>>(index: I): T[I] {
return this.value[index];
}
}
。
答案 0 :(得分:2)
您可以将keyof any[]
中的keyof T
排除在外,而只留下适当的元组键,不幸的是它们将采用字符串形式:
class FormArray<T extends [any, ...any[]]> {
constructor(public value: T) {}
get<I extends Exclude<keyof T, keyof any[]>>(index: I): T[I] {
return this.value[index];
}
}
new FormArray([1,2, 3]).get("0");
您也可以在数字上添加映射,但是恐怕必须手动操作:
interface IndexMap {
0: "0"
1: "1"
2: "2"
3: "3"
4: "4"
/// add up to a resonable number
}
type NumberArrayKeys<T extends PropertyKey> = {
[P in keyof IndexMap]: IndexMap[P] extends T ? P : never
}[keyof IndexMap]
class FormArray<T extends [any, ...any[]]> {
constructor(public value: T) { }
// how to restrict I to only valid index numbers of T ?
get<I extends Exclude<keyof T, keyof any[]> | NumberArrayKeys<keyof T>>(index: I): T[I] {
return this.value[index];
}
}
let a = new FormArray([1, "2", 3]).get("0");
let b = new FormArray([1, "2", 3]).get("1");
let c = new FormArray([1, 2, 3]).get(0); // number
let d = new FormArray([1, "2", 3]).get(1); // string
注意,即使T[I]
返回的索引为I
,即使number
是keyof T
,string
仍然可以使我惊讶不是number
这种认识使我想到了另一个可能的解决方案,其中I
也可以是number
。如果数字在元组长度范围内,它将返回适当的类型,否则,它将返回undefined
。调用不会出错,但是如果您使用undefined
,则返回值将键入为strictNullChecks
,因此您几乎无能为力:
class FormArray<T extends [any, ...any[]]> {
constructor(public value: T) { }
// how to restrict I to only valid index numbers of T ?
get<I extends Exclude<keyof T, keyof any[]> | number>(index: I): T[I] {
return this.value[index];
}
}
let a = new FormArray([1, "2", 3]).get("0");
let b = new FormArray([1, "2", 3]).get("1");
let c = new FormArray([1, 2, 3]).get(0); // number
let d = new FormArray([1, "2", 3]).get(1); // string
let e = new FormArray([1, "2", 3]).get(10); // undefined
答案 1 :(得分:0)
通过引入模板文字字符串,这可以用任意长度元组的一般形式解决。第一步使用与原始解决方案相同的技术,将元组索引提取为字符串文字的并集:
type Indices<A extends any[]> = Exclude<keyof A, keyof any[]> & string;
诀窍在于第二步:我们不尝试将字符串转换为数字,而是进行相反的操作:
type numToStr<N extends number> = `${N}`;
现在,我们需要做的就是从索引联合中Exclude
操作索引,并检查结果类型是否可分配给原始联合。如果不是 - 我们有一个有效的索引,如果是 - 索引不是原始联合的一部分:
type onlyValidIndex<A extends any[], I extends number> = Indices<A> extends Exclude< Indices<A>, numToStr<I> > ? never : I;
瞧!根据您的情况,您将如何使用它:
class FormArray<T extends [any, ...any[]]> {
constructor(public value: T) {}
// how to restrict `I` to only valid index numbers of `T` ?
get<I extends number>(index: onlyValidIndex<T, I>): T[I] {
return this.value[index];
}
}
const fa = new FormArray([ 0, "", false ]);
let a = new FormArray([1, "2", 3]).get("0"); //error
let b = new FormArray([1, "2", 3]).get("1"); //error
let c = new FormArray([1, 2, 3]).get(0); //number
let d = new FormArray([1, "2", 3]).get(1); //string
let e = new FormArray([1, "2", 3]).get(10); //error
请注意,您不能再使用索引的字符串文字版本进行索引,但如果需要,只需放松索引约束以接受辅助类型中的 number | string
联合和辅助类型中的 number | Indices<T>
get
方法签名(因为 string
不能索引 T
)。