Arrayconst-Assertionen für Arrays und TypinferenzIn diesem Kapitel untersuchen wir, wie Arrays in TypeScript typisiert werden können.
Arrays können in JavaScript die folgenden Rollen spielen (entweder eine oder eine Mischung davon)
TypeScript unterstützt diese beiden Rollen, indem es verschiedene Möglichkeiten zur Typisierung von Arrays bietet. Diese werden wir als Nächstes betrachten.
ArrayEin Array-Typ-Literal besteht aus dem Elementtyp gefolgt von []. Im folgenden Code ist das Array-Typ-Literal string[]
// Each Array element has the type `string`:
const myStringArray: string[] = ['fee', 'fi', 'fo', 'fum'];Ein Array-Typ-Literal ist eine Kurzform für die Verwendung des globalen generischen Interface-Typs Array
const myStringArray: Array<string> = ['fee', 'fi', 'fo', 'fum'];Wenn der Elementtyp komplizierter ist, benötigen wir Klammern für Array-Typ-Literale
(number|string)[]
(() => boolean)[]Der generische Typ Array funktioniert in diesem Fall besser
Array<number|string>
Array<() => boolean>Wenn das Array eine feste Länge hat und jedes Element einen anderen, festen Typ hat, der von seiner Position abhängt, können wir Tupel-Typ-Literale wie [string, string, boolean] verwenden.
const yes: [string, string, boolean] = ['oui', 'sí', true];Wenn ein Interface nur eine Index-Signatur hat, können wir es für Arrays verwenden.
interface StringArray {
[index: number]: string;
}
const strArr: StringArray = ['Huey', 'Dewey', 'Louie'];Ein Interface, das sowohl eine Index-Signatur als auch Eigenschafts-Signaturen hat, funktioniert nur für Objekte (da indizierte Elemente und Eigenschaften gleichzeitig definiert werden müssen).
interface FirstNamesAndLastName {
[index: number]: string;
lastName: string;
}
const ducks: FirstNamesAndLastName = {
0: 'Huey',
1: 'Dewey',
2: 'Louie',
lastName: 'Duck',
};Aufgrund der beiden Rollen von Arrays ist es für TypeScript unmöglich, immer den richtigen Typ zu erraten. Betrachten wir als Beispiel das folgende Array-Literal, das der Variablen fields zugewiesen wird.
const fields: Fields = [
['first', 'string', true],
['last', 'string', true],
['age', 'number', false],
];Was ist der beste Typ für fields? Die folgenden sind alle vernünftige Optionen.
type Fields = Array<[string, string, boolean]>;type Fields = Array<[string, ('string'|'number'), boolean]>;type Fields = Array<Array<string|boolean>>;type Fields = [
[string, string, boolean],
[string, string, boolean],
[string, string, boolean],
];type Fields = [
[string, 'string', boolean],
[string, 'string', boolean],
[string, 'number', boolean],
];type Fields = [
Array<string|boolean>,
Array<string|boolean>,
Array<string|boolean>,
];Wenn wir nicht-leere Array-Literale verwenden, ist die Standardeinstellung von TypeScript die Inferenz von Listentypen (nicht Tupeltypen).
// %inferred-type: (string | number)[]
const arr = [123, 'abc'];Leider ist das nicht immer das, was wir wollen.
function func(p: [number, number]) {
return p;
}
// %inferred-type: number[]
const pair1 = [1, 2];
// @ts-expect-error: Argument of type 'number[]' is not assignable to
// parameter of type '[number, number]'. [...]
func(pair1);Wir können dies beheben, indem wir der const-Deklaration eine Typannotation hinzufügen, die die Typinferenz vermeidet.
const pair2: [number, number] = [1, 2];
func(pair2); // OKWenn wir eine Variable mit einem leeren Array-Literal initialisieren, inferiert TypeScript zunächst den Typ any[] und aktualisiert diesen Typ inkrementell, wenn wir Änderungen vornehmen.
// %inferred-type: any[]
const arr1 = [];
arr1.push(123);
// %inferred-type: number[]
arr1;
arr1.push('abc');
// %inferred-type: (string | number)[]
arr1;Beachten Sie, dass der anfängliche inferierte Typ nicht von dem beeinflusst wird, was später passiert.
Wenn wir statt .push() eine Zuweisung verwenden, funktioniert es genauso.
// %inferred-type: any[]
const arr1 = [];
arr1[0] = 123;
// %inferred-type: number[]
arr1;
arr1[1] = 'abc';
// %inferred-type: (string | number)[]
arr1;Wenn das Array-Literal hingegen mindestens ein Element hat, ist der Elementtyp fixiert und ändert sich später nicht.
// %inferred-type: number[]
const arr = [123];
// @ts-expect-error: Argument of type '"abc"' is not assignable to
// parameter of type 'number'. (2345)
arr.push('abc');const-Assertionen für Arrays und TypinferenzWir können ein Array-Literal mit einer const-Assertion suffixen.
// %inferred-type: readonly ["igneous", "metamorphic", "sedimentary"]
const rockCategories =
['igneous', 'metamorphic', 'sedimentary'] as const;Wir erklären, dass rockCategories sich nicht ändern wird. Das hat folgende Auswirkungen:
Das Array wird readonly – wir können keine Operationen verwenden, die es ändern.
// @ts-expect-error: Property 'push' does not exist on type
// 'readonly ["igneous", "metamorphic", "sedimentary"]'. (2339)
rockCategories.push('sand');TypeScript inferiert ein Tupel. Vergleichen Sie
// %inferred-type: string[]
const rockCategories2 = ['igneous', 'metamorphic', 'sedimentary'];TypeScript inferiert Literal-Typen ("igneous" usw.) anstelle von allgemeineren Typen. Das heißt, der inferierte Tupeltyp ist nicht [string, string, string].
Hier sind weitere Beispiele für Array-Literale mit und ohne const-Assertionen.
// %inferred-type: readonly [1, 2, 3, 4]
const numbers1 = [1, 2, 3, 4] as const;
// %inferred-type: number[]
const numbers2 = [1, 2, 3, 4];
// %inferred-type: readonly [true, "abc"]
const booleanAndString1 = [true, 'abc'] as const;
// %inferred-type: (string | boolean)[]
const booleanAndString2 = [true, 'abc'];const-AssertionenEs gibt zwei mögliche Fallstricke bei const-Assertionen.
Erstens ist der inferierte Typ so eng wie möglich. Das verursacht ein Problem für Variablen, die mit let deklariert wurden: Wir können kein anderes Tupel zuweisen als das, das wir zur Initialisierung verwendet haben.
let arr = [1, 2] as const;
arr = [1, 2]; // OK
// @ts-expect-error: Type '3' is not assignable to type '2'. (2322)
arr = [1, 3];Zweitens können Tupel, die über as const deklariert wurden, nicht mutiert werden.
let arr = [1, 2] as const;
// @ts-expect-error: Cannot assign to '1' because it is a read-only
// property. (2540)
arr[1] = 3;Das ist weder ein Vorteil noch ein Nachteil, aber wir müssen uns bewusst sein, dass es passiert.
Immer wenn wir über einen Index auf ein Array-Element zugreifen, geht TypeScript immer davon aus, dass der Index im Bereich liegt (Zeile A).
const messages: string[] = ['Hello'];
// %inferred-type: string
const message = messages[3]; // (A)Aufgrund dieser Annahme ist der Typ von message string. Und nicht undefined oder undefined|string, wie wir vielleicht erwartet hätten.
Wir erhalten einen Fehler, wenn wir einen Tupeltyp verwenden.
const messages: [string] = ['Hello'];
// @ts-expect-error: Tuple type '[string]' of length '1' has no element
// at index '1'. (2493)
const message = messages[1];as const hätte denselben Effekt gehabt, da es zur Inferenz eines Tupeltyps führt.