【问题标题】:How to declare a Fixed length Array in TypeScript如何在 TypeScript 中声明一个固定长度的数组
【发布时间】:2017-04-29 14:46:39
【问题描述】:

冒着证明我对 TypeScript 类型缺乏了解的风险 - 我有以下问题。

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

position: Array<number>;

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

position: Array<3>

感谢任何帮助或澄清!

【问题讨论】:

    标签: typescript types fixed-length-array


    【解决方案1】:

    聚会有点晚了,但如果您使用只读数组,这是一种方法 ([] as const) -

    interface FixedLengthArray<L extends number, T> extends ArrayLike<T> {
      length: L
    }
    
    export const a: FixedLengthArray<2, string> = ['we', '432'] as const
    

    const a 值中添加或删除字符串会导致此错误 -

    Type 'readonly ["we", "432", "fd"]' is not assignable to type 'FixedLengthArray<2, string>'.
      Types of property 'length' are incompatible.
        Type '3' is not assignable to type '2'.ts(2322)
    

    Type 'readonly ["we"]' is not assignable to type 'FixedLengthArray<2, string>'.
      Types of property 'length' are incompatible.
        Type '1' is not assignable to type '2'.ts(2322)
    

    分别。

    【讨论】:

      【解决方案2】:

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

      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'];
      

      编辑(经过很长时间)

      这应该可以处理更大的尺寸(因为基本上它会以指数方式增长数组,直到我们达到最接近的 2 次方):

      type Shift<A extends Array<any>> = ((...args: A) => void) extends ((...args: [A[0], ...infer R]) => void) ? R : never;
      
      type GrowExpRev<A extends Array<any>, N extends number, P extends Array<Array<any>>> = A['length'] extends N ? A : {
        0: GrowExpRev<[...A, ...P[0]], N, P>,
        1: GrowExpRev<A, N, Shift<P>>
      }[[...A, ...P[0]][N] extends undefined ? 0 : 1];
      
      type GrowExp<A extends Array<any>, N extends number, P extends Array<Array<any>>> = A['length'] extends N ? A : {
        0: GrowExp<[...A, ...A], N, [A, ...P]>,
        1: GrowExpRev<A, N, P>
      }[[...A, ...A][N] extends undefined ? 0 : 1];
      
      export type FixedSizeArray<T, N extends number> = N extends 0 ? [] : N extends 1 ? [T] : GrowExp<[T, T], N, [[T]]>;
      

      【讨论】:

      • 元素个数是变量的情况下如何使用?如果我将 N 作为数字类型,将“num”作为数字,那么 const arr: FixedArray = Array.from(new Array(num), (x,i) => i);给我“类型实例化太深而且可能无限”。
      • @MichaSchwab 不幸的是,它似乎只适用于相对较小的数字。否则它会告诉“太多递归”。这同样适用于你的情况。我没有彻底测试它:(。
      • 感谢您回复我!如果您确实遇到了可变长度的解决方案,请告诉我。
      【解决方案3】:

      元组方法:

      此解决方案提供基于元组的严格 FixedLengthArray(又名 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
      

      (*) 此解决方案需要启用 noImplicitAny 打字稿 configuration directive 才能工作(通常推荐的做法)


      Array(ish) 方法:

      此解决方案的行为类似于Array 类型的扩充,接受额外的第二个参数(数组长度)。不像 基于元组的解决方案那样严格和安全。

      语法示例:

      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
      

      【讨论】:

      • 谢谢!但是,仍然可以更改数组的大小而不会出错。
      • var myStringsArray: FixedLengthArray&lt;string, 2&gt; = [ "a", "b" ] // LENGTH ERROR 好像这里的 2 应该是 3?
      • 我已经用更严格的解决方案更新了实现,防止数组长度发生变化
      • @colxi 是否有可能实现允许从 FixedLengthArray 映射到其他 FixedLengthArray ?我的意思的一个例子:const threeNumbers: FixedLengthArray&lt;[number, number, number]&gt; = [1, 2, 3];const doubledThreeNumbers: FixedLengthArray&lt;[number, number, number]&gt; = threeNumbers.map((a: number): number =&gt; a * 2);
      • @AlexMalcolm 恐怕map 为其输出提供了一个通用数组签名。在您的情况下,很可能是 number[] 类型
      【解决方案4】:

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

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

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

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

      Typescript 有 tuple types,它可以让你定义一个具有特定长度和类型的数组:

      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]'
      

      【讨论】:

      • 元组类型仅检查初始大小,因此您仍然可以在初始化后将无限数量的“数字”推送到您的arr
      • 没错,在运行时它仍然是 javascript 到那时“任何事情都会发生”。至少打字稿转译器至少会在源代码中强制执行此操作
      • 如果我想要大数组大小,比如 50,有没有办法用重复类型指定数组大小,比如 [number[50]],这样就不需要写 @ 987654328@50次?
      • 没关系,找到一个关于这个的问题。 stackoverflow.com/questions/52489261/…
      • @VictorZamanian 请注意,如果您超过键入的 TLength,相交 {length: TLength} 的想法不会提供任何打字错误。我还没有找到一个大小强制的 n 长度类型语法。
      猜你喜欢
      • 2012-05-28
      • 2014-06-23
      • 2011-01-19
      • 2016-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多