Comment déclarer un tableau de longueur fixe dans TypeScript


104

Au risque de démontrer mon manque de connaissances sur les types TypeScript - j'ai la question suivante.

Lorsque vous faites une déclaration de type pour un tableau comme celui-ci ...

position: Array<number>;

... cela vous permettra de créer un tableau de longueur arbitraire. Cependant, si vous voulez un tableau contenant des nombres avec une longueur spécifique, c'est-à-dire 3 pour les composants x, y, z, pouvez-vous créer un type avec pour un tableau de longueur fixe, quelque chose comme ça?

position: Array<3>

Toute aide ou clarification appréciée!

Réponses:


164

Le tableau javascript a un constructeur qui accepte la longueur du tableau:

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

Cependant, ce n'est que la taille initiale, il n'y a aucune restriction sur la modification:

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

Typescript a des types de tuple qui vous permettent de définir un tableau avec une longueur et des types spécifiques:

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
Les types de tuple vérifient uniquement la taille initiale, vous pouvez donc toujours envoyer une quantité illimitée de "nombre" à votre arraprès son initialisation.
benjaminz

4
Certes, il est toujours javascript au moment de l'exécution pour "tout se passe" à ce stade. Au moins, le transpilateur dactylographié appliquera cela dans le code source au moins
henryJack

6
Dans le cas où je veux de grandes tailles de tableau comme, par exemple, 50, existe-t-il un moyen de spécifier la taille du tableau avec un type répété, comme [number[50]], de sorte qu'il ne soit pas nécessaire d'écrire [number, number, ... ]50 fois?
Victor Zamanian

2
Qu'à cela ne tienne, j'ai trouvé une question à ce sujet. stackoverflow.com/questions/52489261/…
Victor Zamanian

1
@VictorZamanian Juste pour que vous soyez conscient, l'idée d'intersection {length: TLength}ne fournit aucune erreur de typographie si vous dépassez le typed TLength. Je n'ai pas encore trouvé de syntaxe de type n-longueur appliquée par taille.
Lucas Morgan

22

L'approche Tuple:

Cette solution fournit une signature de type FixedLengthArray stricte (ak.a. SealedArray) basée sur Tuples.

Exemple de syntaxe:

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

C'est l'approche la plus sûre, étant donné qu'elle empêche l'accès aux index en dehors des limites .

La mise en oeuvre :

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

(*) Cette solution nécessite l'activation de la directive de configurationnoImplicitAny typescript pour fonctionner (pratique généralement recommandée)


L'approche Array (ish):

Cette solution se comporte comme une augmentation du Arraytype, acceptant un second paramètre supplémentaire (Array length). N'est pas aussi stricte et sûre que la solution basée sur Tuple .

Exemple de syntaxe:

let foo: FixedLengthArray<string, 3> 

Gardez à l'esprit que cette approche ne vous empêchera pas d'accéder à un index hors des limites déclarées et de lui définir une valeur.

La mise en oeuvre :

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

1
Merci! Cependant, il est toujours possible de modifier la taille du tableau sans obtenir d'erreur.
Eduard

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERRORsemble que 2 devrait être 3 ici?
Qwertiy le

J'ai mis à jour l'implémentation avec une solution plus stricte qui empêche les changements de longueur de tableau
colxi

@colxi Est-il possible d'avoir une implémentation qui permet le mappage de FixedLengthArray à d'autres FixedLengthArray? Un exemple de ce que je veux dire: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 j'ai peur de mapfournir une signature de tableau générique à sa sortie. Dans votre cas, probablement un number[]type
colxi

5

En fait, vous pouvez y parvenir avec le texte dactylographié actuel:

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

Exemples:

// 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
Comment l'utiliser lorsque le nombre d'éléments est une variable? Si j'ai N comme type de nombre et "num" comme nombre, alors const arr: FixedArray <number, N> = Array.from (new Array (num), (x, i) => i); me donne "L'instanciation de type est excessivement profonde et peut-être infinie".
Micha Schwab

2
@MichaSchwab Malheureusement, il semble fonctionner uniquement avec des nombres relativement petits. Sinon, il indique "trop ​​de récursivité". La même chose s'applique à votre cas. Je ne l'ai pas testé à fond :(.
Tomasz Gawel le

Merci de me répondre! Si vous rencontrez une solution pour une longueur variable, merci de me le faire savoir.
Micha Schwab le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.