如何在TypeScript中声明固定长度的数组


104

冒着冒我缺乏对TypeScript类型的知识的风险-我有以下问题。

当您为这样的数组进行类型声明时...

position: Array<number>;

...它将使您可以创建具有任意长度的数组。但是,如果您想要一个包含特定长度数字的数组,即x,y,z分量为3,可以为固定长度的数组创建类型,是这样吗?

position: Array<3>

任何帮助或澄清表示赞赏!

Answers:


164

javascript数组具有一个接受数组长度的构造函数:

let arr = new Array<number>(3);
console.log(arr); // [undefined × 3]

但是,这仅是初始大小,没有更改限制:

arr.push(5);
console.log(arr); // [undefined × 3, 5]

Typescript具有元组类型,可让您定义具有特定长度和类型的数组:

let arr: [number, number, number];

arr = [1, 2, 3]; // ok
arr = [1, 2]; // Type '[number, number]' is not assignable to type '[number, number, number]'
arr = [1, 2, "3"]; // Type '[number, number, string]' is not assignable to type '[number, number, number]'

18
元组类型仅检查初始大小,因此在初始化后,您仍然可以向其中推送无限数量的“数字” arr
benjaminz

4
没错,在那时,“一切”仍然是javascript。至少打字稿编译器将至少在源代码中强制执行此操作
henryJack

6
如果我想要大的数组大小(例如50),是否可以使用重复类型(例如)来指定数组大小[number[50]],这样就不必写[number, number, ... ]50次了?
维克多·扎曼尼安

2
没关系,发现了一个与此有关的问题。stackoverflow.com/questions/52489261/...
维克多Zamanian

1
@VictorZamanian就像您知道的那样,{length: TLength}如果您超过typed ,相交的想法将不会提供任何打字稿错误TLength。我尚未找到大小强制的n长度类型语法。
卢卡斯·摩根

22

元组方法:

该解决方案基于Tuples提供了严格的FixedLengthArray (ak.a. SealedArray)类型签名。

语法示例:

// Array containing 3 strings
let foo : FixedLengthArray<[string, string, string]> 

考虑到它可以防止访问超出范围的索引,因此这是最安全的方法。

实现方式:

type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number
type ArrayItems<T extends Array<any>> = T extends Array<infer TItems> ? TItems : never
type FixedLengthArray<T extends any[]> =
  Pick<T, Exclude<keyof T, ArrayLengthMutationKeys>>
  & { [Symbol.iterator]: () => IterableIterator< ArrayItems<T> > }

测试:

var myFixedLengthArray: FixedLengthArray< [string, string, string]>

// Array declaration tests
myFixedLengthArray = [ 'a', 'b', 'c' ]  // ✅ OK
myFixedLengthArray = [ 'a', 'b', 123 ]  // ✅ TYPE ERROR
myFixedLengthArray = [ 'a' ]            // ✅ LENGTH ERROR
myFixedLengthArray = [ 'a', 'b' ]       // ✅ LENGTH ERROR

// Index assignment tests 
myFixedLengthArray[1] = 'foo'           // ✅ OK
myFixedLengthArray[1000] = 'foo'        // ✅ INVALID INDEX ERROR

// Methods that mutate array length
myFixedLengthArray.push('foo')          // ✅ MISSING METHOD ERROR
myFixedLengthArray.pop()                // ✅ MISSING METHOD ERROR

// Direct length manipulation
myFixedLengthArray.length = 123         // ✅ READ-ONLY ERROR

// Destructuring
var [ a ] = myFixedLengthArray          // ✅ OK
var [ a, b ] = myFixedLengthArray       // ✅ OK
var [ a, b, c ] = myFixedLengthArray    // ✅ OK
var [ a, b, c, d ] = myFixedLengthArray // ✅ INVALID INDEX ERROR

(*)此解决方案要求启用noImplicitAnytypescript 配置指令才能正常工作(通常建议的做法)


Array(ish)方法:

此解决方案表现为Array类型的扩充,接受其他第二个参数(数组长度)。不像基于Tuple的解决方案那么严格和安全。

语法示例:

let foo: FixedLengthArray<string, 3> 

请记住,这种方法不会阻止您访问已声明边界之外的索引并为其设置值。

实现方式:

type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' |  'unshift'
type FixedLengthArray<T, L extends number, TObj = [T, ...Array<T>]> =
  Pick<TObj, Exclude<keyof TObj, ArrayLengthMutationKeys>>
  & {
    readonly length: L 
    [ I : number ] : T
    [Symbol.iterator]: () => IterableIterator<T>   
  }

测试:

var myFixedLengthArray: FixedLengthArray<string,3>

// Array declaration tests
myFixedLengthArray = [ 'a', 'b', 'c' ]  // ✅ OK
myFixedLengthArray = [ 'a', 'b', 123 ]  // ✅ TYPE ERROR
myFixedLengthArray = [ 'a' ]            // ✅ LENGTH ERROR
myFixedLengthArray = [ 'a', 'b' ]       // ✅ LENGTH ERROR

// Index assignment tests 
myFixedLengthArray[1] = 'foo'           // ✅ OK
myFixedLengthArray[1000] = 'foo'        // ❌ SHOULD FAIL

// Methods that mutate array length
myFixedLengthArray.push('foo')          // ✅ MISSING METHOD ERROR
myFixedLengthArray.pop()                // ✅ MISSING METHOD ERROR

// Direct length manipulation
myFixedLengthArray.length = 123         // ✅ READ-ONLY ERROR

// Destructuring
var [ a ] = myFixedLengthArray          // ✅ OK
var [ a, b ] = myFixedLengthArray       // ✅ OK
var [ a, b, c ] = myFixedLengthArray    // ✅ OK
var [ a, b, c, d ] = myFixedLengthArray // ❌ SHOULD FAIL

1
谢谢!但是,仍然可以更改数组的大小而不会出现错误。
爱德华

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERROR好像2应该是3?
Qwertiy

我已经用更严格的解决方案更新了实现,以防止数组长度发生变化
colxi

@colxi是否有可能允许从FixedLengthArray映射到其他FixedLengthArray的实现?我的意思示例:const threeNumbers: FixedLengthArray<[number, number, number]> = [1, 2, 3]; const doubledThreeNumbers: FixedLengthArray<[number, number, number]> = threeNumbers.map((a: number): number => a * 2);
Alex Malcolm

@AlexMalcolm恐怕map会为其输出提供通用数组签名。就您而言,很可能是一种number[]类型
colxi,

5

实际上,您可以使用当前的打字稿来实现:

type Grow<T, A extends Array<T>> = ((x: T, ...xs: A) => void) extends ((...a: infer X) => void) ? X : never;
type GrowToSize<T, A extends Array<T>, N extends number> = { 0: A, 1: GrowToSize<T, Grow<T, A>, N> }[A['length'] extends N ? 0 : 1];

export type FixedArray<T, N extends number> = GrowToSize<T, [], N>;

例子:

// OK
const fixedArr3: FixedArray<string, 3> = ['a', 'b', 'c'];

// Error:
// Type '[string, string, string]' is not assignable to type '[string, string]'.
//   Types of property 'length' are incompatible.
//     Type '3' is not assignable to type '2'.ts(2322)
const fixedArr2: FixedArray<string, 2> = ['a', 'b', 'c'];

// Error:
// Property '3' is missing in type '[string, string, string]' but required in type 
// '[string, string, string, string]'.ts(2741)
const fixedArr4: FixedArray<string, 4> = ['a', 'b', 'c'];

1
当元素数量是变量时,该如何使用?如果我将N作为数字类型,将“ num”作为数字,则使用const arr:FixedArray <number,N> = Array.from(new Array(num),(x,i)=> i); 给我“类型实例化太深,甚至可能是无限的”。
Micha Schwab

2
@MichaSchwab不幸的是,它似乎只适用于相对较小的数字。否则,它表明“太多的递归”。同样适用于您的情况。我没有测试它彻底:(。
托马斯Gawel

谢谢你回到我身旁!如果您遇到可变长度的解决方案,请告诉我。
Micha Schwab
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.