Seit TypeScript Gewerkschaftstypen eingeführt hat, frage ich mich, ob es einen Grund gibt, einen Aufzählungstyp zu deklarieren. Betrachten Sie die folgende Aufzählungstypdeklaration:
enum X { A, B, C }
var x:X = X.A;
und eine ähnliche Erklärung zum Unionstyp:
type X: "A" | "B" | "C"
var x:X = "A";
Wenn sie im Grunde dem gleichen Zweck dienen und die Gewerkschaften mächtiger und ausdrucksvoller sind, warum sind dann Aufzählungen notwendig?
typescript
enums
unions
prmph
quelle
quelle
Antworten:
Soweit ich sehe, sind sie nicht redundant, da Unionstypen nur ein Konzept zur Kompilierungszeit sind, während Aufzählungen tatsächlich transpiliert werden und im resultierenden Javascript ( Beispiel ) landen .
Auf diese Weise können Sie einige Dinge mit Aufzählungen tun, die mit Vereinigungstypen ansonsten nicht möglich sind (z. B. Aufzählung der möglichen Aufzählungswerte ).
quelle
enum
. Der erste Satz ist verwirrend. "Soweit ich sehe, sind [Aufzählungen] nicht ..." Beantwortung der Fragen "gibt es [einen] Grund, einen Aufzählungstyp zu deklarieren."Mit den neuesten Versionen von TypeScript ist es einfach, iterierbare Vereinigungstypen zu deklarieren. Daher sollten Sie Unionstypen Enums vorziehen.
So deklarieren Sie iterierbare Vereinigungstypen
const permissions = ['read', 'write', 'execute'] as const; type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute' // you can iterate over permissions for (const permission of permissions) { // do something }
Wenn sich die tatsächlichen Werte des Unionstyps nicht sehr gut beschreiben, können Sie sie wie bei Aufzählungen benennen.
// when you use enum enum Permission { Read = 'r', Write = 'w', Execute = 'x' } // union type equivalent const Permission = { Read: 'r', Write: 'w', Execute: 'x' } as const; type Permission = typeof Permission[keyof typeof Permission]; // 'r' | 'w' | 'x' // of course it's quite easy to iterate over for (const permission of Object.values(Permission)) { // do something }
Verpassen Sie nicht die
as const
Behauptung, die in diesen Mustern die entscheidende Rolle spielt.Warum ist es nicht gut, Aufzählungen zu verwenden?
1. Nicht konstante Aufzählungen passen nicht zum Konzept "eine typisierte Obermenge von JavaScript".
Ich denke, dieses Konzept ist einer der entscheidenden Gründe, warum TypeScript unter anderen altJS-Sprachen so beliebt geworden ist. Nicht konstante Enums verletzen das Konzept, indem sie zur Laufzeit lebende JavaScript-Objekte mit einer Syntax ausgeben, die nicht mit JavaScript kompatibel ist.
2. Konstanten haben einige Fallstricke
Konstanten können mit Babel nicht transpiliert werden
Derzeit gibt es zwei Problemumgehungen für dieses Problem: Sie müssen Konstanten manuell oder mit dem Plugin entfernen
babel-plugin-const-enum
.Das Deklarieren von Konstanten in einem Umgebungskontext kann problematisch sein
Umgebungskonstanten sind nicht zulässig, wenn das
--isolatedModules
Flag bereitgestellt wird. Ein TypeScript-Teammitglied sagt, dass "const enum
in DT wirklich keinen Sinn ergibt" (DT bezieht sich auf DefinitelyTyped) und "Sie sollten stattdessen einen Union-Typ von Literalen (Zeichenfolge oder Zahl) verwenden" anstelle von Konstanten im Umgebungskontext.Konstanten unter
--isolatedModules
Flagge verhalten sich auch außerhalb eines Umgebungskontexts seltsamIch war überrascht, diesen Kommentar auf GitHub zu lesen und bestätigte, dass das Verhalten mit TypeScript 3.8.2 immer noch stimmt.
3. Numerische Aufzählungen sind nicht typsicher
Sie können numerischen Aufzählungen eine beliebige Nummer zuweisen.
enum ZeroOrOne { Zero = 0, One = 1 } const zeroOrOne: ZeroOrOne = 2; // no error!!
4. Die Deklaration von String-Aufzählungen kann redundant sein
Wir sehen manchmal diese Art von String-Aufzählungen:
enum Day { Sunday = 'Sunday', Monday = 'Monday', Tuesday = 'Tuesday', Wednesday = 'Wednesday', Thursday = 'Thursday', Friday = 'Friday', Saturday = 'Saturday' }
5. Das Muster der Vereinigungstypen ist viel cooler
Die Muster, die ich am Anfang dieser Antwort gezeigt habe, sind die großartigen Beispiele aus der Praxis, wie flexibel und ausdrucksstark TypeScript ist. Ich glaube, Sie können TypeScript besser verstehen und ein guter TypeScript-Programmierer sein, wenn Sie solche Muster aggressiv anwenden.
Ich muss zugeben, dass es eine Aufzählungsfunktion gibt, die von Gewerkschaftstypen nicht erreicht wird
Auch wenn aus dem Kontext ersichtlich ist, dass der Zeichenfolgenwert in der Aufzählung enthalten ist, können Sie ihn nicht der Aufzählung zuweisen.
enum StringEnum { Foo = 'foo' } const foo1: StringEnum = StringEnum.Foo; // no error const foo2: StringEnum = 'foo'; // error!!
Dies vereinheitlicht den Stil der Enum-Wertzuweisung im gesamten Code, indem die Verwendung von Zeichenfolgenwerten oder Zeichenfolgenliteralen vermieden wird. Dieses Verhalten stimmt nicht mit dem Verhalten des TypeScript-Typsystems an den anderen Stellen überein und ist etwas überraschend. Einige Leute, die dachten, dies sollte behoben werden, werfen Probleme auf ( dies und das ), in denen wiederholt erwähnt wird, dass die Absicht von String-Aufzählungen besteht um "undurchsichtige" Zeichenfolgentypen bereitzustellen: dh sie können geändert werden, ohne die Verbraucher zu ändern.
enum Weekend { Saturday = 'Saturday', Sunday = 'Sunday' } // As this style is forced, you can change the value of // Weekend.Saturday to 'Sat' without modifying consumers const weekend: Weekend = Weekend.Saturday;
Beachten Sie, dass diese "Undurchsichtigkeit" nicht perfekt ist, da die Zuweisung von Aufzählungswerten zu Zeichenfolgenliteraltypen nicht beschränkt ist.
enum Weekend { Saturday = 'Saturday', Sunday = 'Sunday' } // The change of the value of Weekend.Saturday to 'Sat' // results in a compilation error const saturday: 'Saturday' = Weekend.Saturday;
Wenn Sie der Meinung sind, dass diese "undurchsichtige" Funktion so wertvoll ist, dass Sie alle oben beschriebenen Nachteile im Austausch dafür akzeptieren können, können Sie Zeichenfolgenaufzählungen nicht aufgeben.
So entfernen Sie Aufzählungen aus Ihrer Codebasis
Mit der
no-restricted-syntax
Regel von ESLint, wie beschrieben .quelle
declare const permissions: readonly ['read', 'write', 'execute']
als auch des realen JavaScript-Objektsconst permissions = ['read', 'write', 'execute']
sollte funktionieren.const foo: StringEnum === StringEnum.Foo // true or false
Ich möchte, dass diese Einschränkung sicherstellt, dass wir keine Verwechslungen von String-Literalen haben.Es gibt nur wenige Gründe, warum Sie eine verwenden möchten
enum
enum
.enum
as verwendenflags
. Bit FlagsIch sehe die großen Vorteile der Verwendung einer Union darin, dass sie eine prägnante Möglichkeit bieten, einen Wert mit mehreren Typen darzustellen, und dass sie sehr gut lesbar sind.
let x: number | string
BEARBEITEN: Ab TypeScript 2.4 unterstützen Enums jetzt Zeichenfolgen.
enum Colors { Red = "RED", Green = "GREEN", Blue = "BLUE", }
quelle
enum
von "Bit-Flags" ein Anti-Pattern ist?Aufzählungen können konzeptionell als Teilmenge von Vereinigungstypen betrachtet werden, die bestimmten Werten
int
und / oderstring
Werten gewidmet sind, wobei einige zusätzliche Funktionen in anderen Antworten erwähnt werden, die sie benutzerfreundlich machen, z . B. Namespace .In Bezug auf die Typensicherheit sind numerische Aufzählungen weniger sicher, dann kommen Vereinigungstypen und schließlich Zeichenfolgenaufzählungen:
// Numeric enum enum Colors { Red, Green, Blue } const c: Colors = 100; // ⚠️ No errors! // Equivalent union types type Color = | 0 | 'Red' | 1 | 'Green' | 2 | 'Blue'; let color: Color = 'Red'; // ✔️ No error because namespace free color = 100; // ✔️ Error: Type '100' is not assignable to type 'Color' type AltColor = 'Red' | 'Yellow' | 'Blue'; let altColor: AltColor = 'Red'; color = altColor; // ⚠️ No error because `altColor` type is here narrowed to `"Red"` // String enum enum NamedColors { Red = 'Red', Green = 'Green', Blue = 'Blue', } let namedColor: NamedColors = 'Red'; // ✔️ Error: Type '"Red"' is not assignable to type 'Colors'. enum AltNamedColors { Red = 'Red', Yellow = 'Yellow', Blue = 'Blue', } namedColor = AltNamedColors.Red; // ✔️ Error: Type 'AltNamedColors.Red' is not assignable to type 'Colors'.
Mehr zu diesem Thema in diesem 2ality-Artikel: TypeScript-Enums: Wie funktionieren sie? Wofür können sie verwendet werden?
Unionstypen unterstützen heterogene Daten und Strukturen und ermöglichen beispielsweise Polymorphismus:
class RGB { constructor( readonly r: number, readonly g: number, readonly b: number) { } toHSL() { return new HSL(0, 0, 0); // Fake formula } } class HSL { constructor( readonly h: number, readonly s: number, readonly l: number) { } lighten() { return new HSL(this.h, this.s, this.l + 10); } } function lightenColor(c: RGB | HSL) { return (c instanceof RGB ? c.toHSL() : c).lighten(); }
Zwischen Aufzählungen und Vereinigungstypen können Singletons Aufzählungen ersetzen. Es ist ausführlicher, aber auch objektorientierter :
class Color { static readonly Red = new Color(1, 'Red', '#FF0000'); static readonly Green = new Color(2, 'Green', '#00FF00'); static readonly Blue = new Color(3, 'Blue', '#0000FF'); static readonly All: readonly Color[] = [ Color.Red, Color.Green, Color.Blue, ]; private constructor( readonly id: number, readonly label: string, readonly hex: string) { } } const c = Color.Red; const colorIds = Color.All.map(x => x.id);
Ich neige dazu, F # zu betrachten, um gute Modellierungspraktiken zu sehen. Ein Zitat aus einem Artikel über F # -Aufzählungen auf F # für Spaß und Gewinn , das hier nützlich sein kann:
Es gibt andere Alternativen zu Modellaufzählungen. Einige von ihnen werden in diesem anderen 2ality-Artikel ausführlich beschrieben. Alternativen zu Aufzählungen in TypeScript .
quelle
Der Aufzählungstyp ist nicht redundant, aber in den meisten Fällen wird die Vereinigung bevorzugt.
Aber nicht immer. Die Verwendung von Aufzählungen zur Darstellung von z. B. Zustandsübergängen könnte viel praktischer und aussagekräftiger sein als die Verwendung von union **
Betrachten Sie ein reales Live-Szenario:
enum OperationStatus { NEW = 1, PROCESSING = 2, COMPLETED = 4 } OperationStatus.PROCESSING > OperationStatus.NEW // true OperationStatus.PROCESSING > OperationStatus.COMPLETED // false
quelle