Come dichiarare un array a lunghezza fissa in TypeScript


104

A rischio di dimostrare la mia mancanza di conoscenza sui tipi TypeScript, ho la seguente domanda.

Quando fai una dichiarazione di tipo per un array come questo ...

position: Array<number>;

... ti permetterà di creare un array con lunghezza arbitraria. Tuttavia, se si desidera un array contenente numeri con una lunghezza specifica, ad esempio 3 per i componenti x, y, z, è possibile creare un tipo con un array a lunghezza fissa, qualcosa del genere?

position: Array<3>

Qualsiasi aiuto o chiarimento apprezzato!

Risposte:


164

L'array javascript ha un costruttore che accetta la lunghezza dell'array:

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

Tuttavia, questa è solo la dimensione iniziale, non ci sono limitazioni per modificarla:

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

Typescript ha tipi di tupla che ti consentono di definire un array con una lunghezza e tipi specifici:

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
I tipi di tupla controllano solo la dimensione iniziale, quindi puoi ancora inviare una quantità illimitata di "numero" al tuo arrdopo che è stato inizializzato.
Benjaminz

4
È vero, è ancora javascript in fase di esecuzione per "tutto va bene" a quel punto. Almeno il traduttore di dattiloscritto lo imporrà almeno nel codice sorgente
henryJack

6
Nel caso in cui desidero dimensioni di array di grandi dimensioni come, diciamo, 50, c'è un modo per specificare la dimensione dell'array con un tipo ripetuto, come [number[50]], in modo che non sia necessario scrivere [number, number, ... ]50 volte?
Victor Zamanian

2
Non importa, ho trovato una domanda in merito. stackoverflow.com/questions/52489261/...
Victor Zamanian

1
@VictorZamanian Solo così sei consapevole, l'idea di intersecare {length: TLength}non fornisce alcun errore di battitura se superi il dattiloscritto TLength. Non ho ancora trovato una sintassi di tipo n-length applicata alla dimensione.
Lucas Morgan

22

L'approccio Tuple:

Questa soluzione fornisce una firma di tipo FixedLengthArray (nota anche come SealedArray) basata su tuple.

Esempio di sintassi:

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

Questo è l'approccio più sicuro, considerando che impedisce l'accesso agli indici al di fuori dei confini .

Implementazione :

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

Test:

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

(*) Questa soluzione richiede che la direttiva di configurazione del noImplicitAnydattiloscritto sia abilitata per funzionare (pratica comunemente consigliata)


L'approccio Array (ish):

Questa soluzione si comporta come un ampliamento del Arraytipo, accettando un secondo parametro aggiuntivo (Array length). Non è così rigoroso e sicuro come la soluzione basata su Tuple .

Esempio di sintassi:

let foo: FixedLengthArray<string, 3> 

Tieni presente che questo approccio non ti impedirà di accedere a un indice al di fuori dei limiti dichiarati e di impostare un valore su di esso.

Implementazione :

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

Test:

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
Grazie! Tuttavia, è ancora possibile modificare la dimensione dell'array senza ottenere un errore.
Eduard

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERRORsembra che 2 dovrebbe essere 3 qui?
Qwertiy,

Ho aggiornato l'implementazione con una soluzione più rigorosa che impedisce le modifiche alla lunghezza dell'array
colxi

@colxi È possibile avere un'implementazione che consenta la mappatura da FixedLengthArray ad altri FixedLengthArray? Un esempio di cosa intendo: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 temo mapfornisca una firma di array generica al suo output. Nel tuo caso molto probabilmente un number[]tipo
colxi

5

In realtà, puoi farlo con l'attuale dattiloscritto:

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

Esempi:

// 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
Come lo uso quando il numero di elementi è una variabile? Se ho N come tipo di numero e "num" come numero, allora const arr: FixedArray <number, N> = Array.from (new Array (num), (x, i) => i); mi dà "L'istanziazione del tipo è eccessivamente profonda e forse infinita".
Micha Schwab

2
@MichaSchwab Sfortunatamente sembra funzionare solo con numeri relativamente piccoli. Altrimenti dice "troppa ricorsione". Lo stesso vale per il tuo caso. Non l'ho testato a fondo :(.
Tomasz Gawel

Grazie per essere tornato da me! Se trovi una soluzione per la lunghezza variabile, fatemelo sapere.
Micha Schwab
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.