Menu

TypeScript Enum: numerische, String- und const Enums

Ein TypeScript-Enum ist eine benannte Menge von Konstanten, etwa enum Direction { Up, Down }. Numerische und String-Enums, das JavaScript, in das ein Enum kompiliert wird, Reverse Mapping, über ein Enum iterieren, const Enums und wann eine Union aus String-Literalen oder ein as const Objekt die bessere Wahl ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Ein TypeScript-Enum ist eine benannte Menge von Konstanten. enum Direction { Up, Down, Left, Right } erzeugt sowohl einen Typ, Direction, als auch ein Objekt zur Laufzeit, auf dessen Member du mit Direction.Up zugreifst. Die Member werden ab 0 durchnummeriert, wenn du ihnen keine Werte gibst, und String-Enums geben jedem Member einen lesbaren String.

Enums gehören zu den wenigen TypeScript-Features, die nicht nur Typen sind: Ein Enum wird beim Kompilieren zu einem echten JavaScript-Objekt.

Numerische Enums

Ohne Initialisierer bekommen die Member 0, 1, 2 und so weiter. Gibst du dem ersten Member eine Zahl, zählen die übrigen von dort weiter. Du kannst auch jeden Wert ausdrücklich setzen, was die sichere Wahl ist, wenn die Zahlen in einer Datenbank gespeichert oder über das Netzwerk gesendet werden.

Auf die automatische Nummerierung zu setzen ist in Ordnung für Werte, die das Programm nie verlassen. Kann sich die Reihenfolge der Member ändern und werden die Zahlen irgendwo gespeichert, nummeriert ein in der Mitte eingefügter Member stillschweigend alles danach um.

Wozu ein Enum kompiliert wird

Typen werden entfernt, ein Enum aber nicht. Das ist das JavaScript, das TypeScript für ein numerisches und ein String-Enum erzeugt:

enum Direction { Up, Down, Left, Right }
enum Status { Active = "ACTIVE", Inactive = "INACTIVE" }
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
var Status;
(function (Status) {
    Status["Active"] = "ACTIVE";
    Status["Inactive"] = "INACTIVE";
})(Status || (Status = {}));

Direction["Up"] = 0 gibt 0 zurück, also wird in derselben Anweisung Direction[0] = "Up" gesetzt. Ein numerisches Enum bildet daher in beide Richtungen ab: vom Namen zur Zahl und von der Zahl zurück zum Namen. Das ist das Reverse Mapping. String-Enums bilden nur Namen auf Werte ab.

Das ausgegebene Objekt Direction hat acht Schlüssel: die vier Namen und die vier Zahlen. Das wird wichtig, sobald du darüber iterierst.

String-Enums

Jeder Member eines String-Enums braucht einen ausdrücklichen String-Wert. Die Werte erscheinen unverändert in Logs, JSON und Datenbanken, wodurch String-Enums leichter zu debuggen sind als Zahlen.

Ein String-Enum ist in einer Hinsicht nominal, die viele überrascht: Ein einfacher String ist ihm nicht zuweisbar, selbst wenn der Text dem Wert eines Members entspricht.

index.ts(7,5): error TS2820: Type '"ACTIVE"' is not assignable to type 'Status'. Did you mean 'Status.Inactive'?

(Der Vorschlag in der Meldung ist eine Vermutung des Compilers und hier falsch; die Lösung ist Status.Active.) In die andere Richtung kann ein Status-Wert überall verwendet werden, wo ein string erwartet wird. Kommen Werte als Strings an, aus JSON oder einem Formular, wandle sie mit einer Prüfung um wie im Abschnitt zum Prüfen von Werten weiter unten.

Ein Enum als Typ verwenden

Der Name des Enums ist ein Typ, dessen Werte seine Member sind. Zusammen mit switch prüft TypeScript, dass jeder Member behandelt wird, wenn die Funktion einen Wert zurückgeben muss:

Wird Shape ein neuer Member ohne neuen case hinzugefügt, kompiliert sides nicht mehr und meldet TS2366, Function lacks ending return statement and return type does not include 'undefined'. Die Seite zu switch zeigt die strengere Vollständigkeitsprüfung mit never.

Die letzten Zeilen zeigen eine echte Schwäche numerischer Enums. Ein Zahlenliteral, das zu keinem Member passt, const level: Level = 99, ist ein Compilerfehler (TS2322), aber jeder Wert vom Typ number wird akzeptiert, also kommt 57 durch. String-Enums haben diese Lücke nicht.

Über ein Enum iterieren

Ein Enum ist zur Laufzeit ein Objekt, daher funktionieren Object.keys, Object.values und Object.entries. Bei einem String-Enum liefern sie genau die Member. Bei einem numerischen Enum liefern sie auch die Einträge des Reverse Mappings, die du herausfilterst:

Um eine Variable als „einer der Membernamen des Enums“ zu typisieren, nimm keyof typeof Direction, also die Union "Up" | "Down" | "Left" | "Right". Dann schlägt Direction[name] den Wert mit voller Typsicherheit nach.

Ein String-Enum hat kein Reverse Mapping. Um aus einem Wert den Namen eines Members zu bekommen, durchsuchst du die Einträge: Object.entries(Status).find(([, v]) => v === "ACTIVE")?.[0] ist "Active" oder undefined, wenn kein Member diesen Wert hat.

Prüfen, ob ein Wert im Enum liegt

Daten von außerhalb des Programms sind ein einfacher string oder eine number. Ein Type Guard prüft sie gegen die Werte des Enums und engt sie auf den Enum-Typ ein:

Vermeide raw as Status bei nicht vertrauenswürdigen Eingaben: Die Assertion kompiliert, aber zur Laufzeit wird nichts geprüft, also würde "DELETED" als gültiger Status typisiert durch das Programm wandern.

const Enums

const enum bittet den Compiler, das Enum zu löschen und den Wert jedes Members dort einzusetzen, wo er verwendet wird. Zur Laufzeit gibt es kein Objekt, also kann nichts iteriert oder rückwärts abgebildet werden.

const enum spart ein paar Bytes und einen Eigenschaftszugriff, setzt aber voraus, dass der Compiler die Deklaration des Enums sieht, wenn er jede Datei kompiliert, die es nutzt. Tools, die Datei für Datei transpilieren, etwa Babel und swc, sehen kein const Enum, das in einer anderen Datei deklariert ist; das Type Stripping von Node lehnt const Enums wie jedes andere Enum ab; und mit isolatedModules oder verbatimModuleSyntax meldet TypeScript den Fehler TS2748, wenn du ein const Enum aus einer Deklarationsdatei verwendest. Der meiste Anwendungscode braucht keine const Enums.

Enum vs Union-Typ vs as const Objekt

Es gibt drei gängige Wege, eine feste Menge von Werten zu definieren:

enumUnion aus Literalenas const Objekt
Existiert zur Laufzeitja, ein Objektneinja, ein einfaches Objekt
Über die Werte iterierenObject.values (numerisch: filtern)nein, nichts zu iterierenObject.values
Akzeptiert ein einfaches "red"nein (String-Enums)jaja
Benannter Zugriff X.Redjaneinja
Reverse Mappingnur numerische Enumsneinnein
Läuft mit Type Stripping in Nodeneinjaja
Erlaubt durch erasableSyntaxOnlyneinjaja
Zusätzliche Syntax zu lernenEnum-Regeln, const Enumskeinedas typeof-Muster

Viele Teams nehmen heute standardmäßig eine Union aus String-Literalen und wechseln zum as const Objekt, wenn sie die Werte zur Laufzeit brauchen (um darüber zu iterieren oder ein Dropdown zu bauen). Die Gründe: Unions sind reine Typen und verschwinden aus der Ausgabe; sie akzeptieren die einfachen Strings, die JSON und APIs liefern; und Enums sind das eine Stück alltägliches TypeScript, das nicht „JavaScript plus entfernbare Typen“ ist.

Dieser letzte Punkt ist inzwischen praktisch relevant. Node führt .ts-Dateien direkt aus, indem es die Typen entfernt, und ein Enum lässt sich nicht einfach entfernen:

node status.ts
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode

Mit dem Flag --experimental-transform-types laufen Enums in Node, und die Compileroption erasableSyntaxOnly meldet jedes Enum als Fehler TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., sodass ein Projekt sie von vornherein verbieten kann. Wie Type Stripping funktioniert, steht unter TypeScript ausführen. Nichts davon macht Enums falsch: Code, der mit tsc oder einem Bundler kompiliert wird, führt sie problemlos aus, und eine Codebasis, die Enums bereits nutzt, gewinnt wenig durch eine Umstellung.

Häufig gestellte Fragen

Was ist ein Enum in TypeScript?

Eine benannte Menge von Konstanten, die zugleich ein Typ und ein Objekt zur Laufzeit ist: Mit enum Direction { Up, Down } kannst du Direction.Up schreiben und Direction als Parametertyp verwenden. Anders als die meisten TypeScript-Features wird ein Enum nicht entfernt: Es wird zu einem JavaScript-Objekt kompiliert, das zur Laufzeit existiert.

Wie iteriere ich in TypeScript über ein Enum?

Bei einem String-Enum liefert Object.values(MyEnum) die Werte und Object.keys(MyEnum) die Namen. Ein numerisches Enum enthält auch Einträge des Reverse Mappings ("0": "Up"), filtere diese also heraus: Object.keys(Direction).filter((k) => isNaN(Number(k))) liefert nur die Namen. Über ein const enum kann man nicht iterieren, weil es zur Laufzeit nicht existiert.

Wie wandle ich in TypeScript einen String in einen Enum-Wert um?

Prüfe den String in einem Type Guard gegen die Werte des Enums: function isStatus(s: string): s is Status { return (Object.values(Status) as string[]).includes(s); }. Nach der Prüfung hat s den Typ Status. Ein einfaches s as Status kompiliert, prüft zur Laufzeit aber nichts.

Sollte ich in TypeScript ein Enum oder einen Union-Typ verwenden?

Viele Teams bevorzugen eine Union aus String-Literalen (type Status = "active" | "inactive") oder ein as const Objekt, wenn sie die Werte auch zur Laufzeit brauchen. Unions werden vollständig entfernt, funktionieren mit dem eingebauten Type Stripping von Node und der Option erasableSyntaxOnly und akzeptieren einfache Strings wie "active". Enums sind aber auch in Ordnung, besonders in Codebasen, die sie bereits nutzen.

Was ist der Unterschied zwischen enum und const enum?

Ein normales Enum wird zu einem Objekt kompiliert, über das man zur Laufzeit iterieren und in dem man nachschlagen kann. Ein const enum wird beim Kompilieren entfernt, und jede Verwendung wird durch ihren Wert ersetzt (Size.Large wird zu 2). Es kostet zur Laufzeit also nichts, lässt sich aber nicht iterieren, und Tools, die Datei für Datei kompilieren, schränken es ein.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S