So deklarieren Sie ein Array mit fester Länge in TypeScript


103

Auf die Gefahr hin, mein mangelndes Wissen über TypeScript-Typen zu demonstrieren, habe ich die folgende Frage.

Wenn Sie eine Typdeklaration für ein Array wie dieses erstellen ...

position: Array<number>;

... damit können Sie ein Array mit beliebiger Länge erstellen. Wenn Sie jedoch ein Array mit Zahlen mit einer bestimmten Länge, dh 3 für x-, y- und z-Komponenten, möchten, können Sie einen Typ mit einem Array mit fester Länge erstellen.

position: Array<3>

Jede Hilfe oder Klarstellung geschätzt!

Antworten:


165

Das Javascript-Array verfügt über einen Konstruktor, der die Länge des Arrays akzeptiert:

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

Dies ist jedoch nur die Anfangsgröße. Es gibt keine Einschränkung, dies zu ändern:

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

Typescript verfügt über Tupeltypen, mit denen Sie ein Array mit einer bestimmten Länge und bestimmten Typen definieren können:

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

17
Die Tupeltypen überprüfen nur die Anfangsgröße, sodass Sie arrnach der Initialisierung immer noch eine unbegrenzte Anzahl von "Zahlen" an Ihre senden können.
Benjamin

4
Es stimmt, es ist zur Laufzeit immer noch Javascript, um zu diesem Zeitpunkt "alles geht". Zumindest der Typoskript-Transpiler wird dies mindestens im Quellcode erzwingen
henryJack

6
Wenn ich große Arraygrößen wie z. B. 50 möchte, gibt es eine Möglichkeit, die Arraygröße mit einem wiederholten Typ wie anzugeben [number[50]], damit nicht [number, number, ... ]50 Mal geschrieben werden muss?
Victor Zamanian


1
@VictorZamanian Nur damit Sie wissen, dass die Idee zum Überschneiden {length: TLength}keinen Typoskriptfehler liefert, wenn Sie den eingegebenen Wert überschreiten TLength. Ich habe noch keine größenerzwungene Syntax vom Typ n-Länge gefunden.
Lucas Morgan

21

Der Tupel-Ansatz:

Diese Lösung bietet eine strikte Signatur vom Typ FixedLengthArray (ak.a. SealedArray), die auf Tupeln basiert.

Syntaxbeispiel:

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

Dies ist der sicherste Ansatz, da er den Zugriff auf Indizes außerhalb der Grenzen verhindert .

Implementierung :

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

(*) Für diese Lösung muss die noImplicitAnyTypenskript- Konfigurationsanweisung aktiviert sein, damit sie funktioniert (allgemein empfohlene Vorgehensweise).


Der Array (ish) -Ansatz:

Diese Lösung verhält sich wie eine Erweiterung des ArrayTyps und akzeptiert einen zusätzlichen zweiten Parameter (Array-Länge). Ist nicht so streng und sicher wie die Tuple-basierte Lösung .

Syntaxbeispiel:

let foo: FixedLengthArray<string, 3> 

Beachten Sie, dass dieser Ansatz Sie nicht daran hindert, außerhalb der deklarierten Grenzen auf einen Index zuzugreifen und einen Wert darauf festzulegen.

Implementierung :

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
Vielen Dank! Es ist jedoch weiterhin möglich, die Größe des Arrays zu ändern, ohne dass ein Fehler auftritt.
Eduard

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERRORscheint wie 2 sollte 3 hier sein?
Qwertiy

Ich habe die Implementierung mit einer strengeren Lösung aktualisiert, die Änderungen der Array-Länge verhindert
colxi

@colxi Ist es möglich, eine Implementierung zu haben, die die Zuordnung von FixedLengthArray zu anderen FixedLengthArray ermöglicht? Ein Beispiel für das, was ich meine: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 Ich fürchte, es mapgibt eine generische Array-Signatur für seine Ausgabe. In Ihrem Fall höchstwahrscheinlich ein number[]Typ
Colxi

4

Tatsächlich können Sie dies mit dem aktuellen Typoskript erreichen:

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

Beispiele:

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

Wie verwende ich das, wenn die Anzahl der Elemente eine Variable ist? Wenn ich N als Nummerntyp und "num" als Nummer habe, dann const arr: FixedArray <Nummer, N> = Array.from (neues Array (num), (x, i) => i); gibt mir "Typ Instanziierung ist übermäßig tief und möglicherweise unendlich".
Micha Schwab

2
@MichaSchwab Leider scheint es nur mit relativ kleinen Zahlen zu funktionieren. Andernfalls wird "zu viel Rekursion" angezeigt. Gleiches gilt für Ihren Fall. Ich habe es nicht gründlich getestet :(.
Tomasz Gawel

Vielen Dank, dass Sie sich bei mir gemeldet haben! Wenn Sie auf eine Lösung für variable Länge stoßen, lassen Sie es mich bitte wissen.
Micha Schwab
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.