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.