How to declare a Fixed length Array in TypeScript

前端 未结 3 781
情书的邮戳
情书的邮戳 2020-12-02 13:02

At the risk of demonstrating my lack of knowledge surrounding TypeScript types - I have the following question.

When you make a type declaration for an array like th

相关标签:
3条回答
  • 2020-12-02 13:13

    Actually, You can achieve this with current typescript:

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

    Examples:

    // 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'];
    
    0 讨论(0)
  • 2020-12-02 13:24

    The javascript array has a constructor that accepts the length of the array:

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

    However, this is just the initial size, there's no restriction on changing that:

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

    Typescript has tuple types which let you define an array with a specific length and 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]'
    
    0 讨论(0)
  • 2020-12-02 13:31

    The Tuple approach :

    This solution provides a strict FixedLengthArray (ak.a. SealedArray) type signature based in Tuples.

    Syntax example :

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

    This is the safest approach, considering it prevents accessing indexes out of the boundaries.

    Implementation :

    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> > }
    

    Tests :

    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
    

    (*) This solution requires the noImplicitAny typescript configuration directive to be enabled in order to work (commonly recommended practice)


    The Array(ish) approach :

    This solution behaves as an augmentation of the Array type, accepting an additional second parameter(Array length). Is not as strict and safe as the Tuple based solution.

    Syntax example :

    let foo: FixedLengthArray<string, 3> 
    

    Keep in mind that this approach will not prevent you from accessing an index out of the declared boundaries and set a value on it.

    Implementation :

    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>   
      }
    

    Tests :

    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
    
    0 讨论(0)
提交回复
热议问题