如何将方法参数限制为元组类型的索引号?

Joh*_*ohn 4 typescript typescript-generics

我有一个泛型类,其中类型参数是一个元组。我在类上创建一个方法时遇到问题,该方法的参数仅限于元组的索引。

例如(游乐场链接):

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];
  }
}
Run Code Online (Sandbox Code Playgroud)

我知道您可以做的是keyof获取元组上的所有属性,其中包括与元组包含的对象关联的键(即“0”、“1”等)。不幸的是,keyof会拉入元组的所有属性,包括“长度”、“拼接”等。

我尝试使用keyof并排除所有不属于 type 的属性number,但后来我意识到索引属性(“0”、“1”等)是由keyofas type返回的string

目前可以在 TypeScript 中完成此任务吗?

Tit*_*mir 5

您可以排除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");
Run Code Online (Sandbox Code Playgroud)

您还可以添加到数字的映射,但恐怕这必须是手动操作:


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

Run Code Online (Sandbox Code Playgroud)

请注意,我很惊讶T[I]即使I是 a number即使keyof T返回索引为stringnotnumber

这种认识使我想到了另一种可能的解决方案,其中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


Run Code Online (Sandbox Code Playgroud)