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.