Enums
In an office building's elevator, the buttons read "Parking", "Lobby" and "Roof". A person presses a button by its name, while the elevator receives a floor number: 0, 1 or 2. Names are handy for people, numbers for the machine, and the buttons link one to the other.
Programs are full of such sets: difficulty levels, order statuses, user roles. You could write bare numbers 0, 1 and 2 in the code, but a month later nobody will remember what 2 means. For named sets of values TypeScript has the enum, declared with the enum keyword.
enum Level {
Low,
Mid,
High,
}
function describe(level: Level) {
console.log(level);
}
describe(Level.Mid); // 1
describe(Level.High); // 2Let's walk through the example line by line:
enum Level { … }declares an enum with three members:Low,MidandHigh.- The members get numbers in order, starting from zero:
Lowis0,Midis1,Highis2. level: Level—the enum's name works as a type: the parameter takes members ofLevel, and tsc rejectsdescribe(7): no member ofLevelhas the value7. A number equal to a member's value, such asdescribe(1), passes.describe(Level.Mid)prints1, notMid: a member's value is a number.
Member values
Here the elevator picture ends. The factory chose the button numbers, but you can set the values of enum members yourself—numbers and strings alike.
enum Priority {
Low = 1,
Mid,
High,
}
enum Color {
Red = "RED",
Green = "GREEN",
}
console.log(Priority.Mid, Priority.High); // 2 3
console.log(Color.Green); // GREENYour own numbers
Low = 1 sets the first member's value by hand. A member without a value gets the previous member's value plus one, so Mid is 2 and High is 3. This rule works anywhere in the enum, not only at the start.
String values
Red = "RED" makes the enum a string enum. Such values read well in logs and in data from a server. Counting on from the previous member does not work for strings, so every member gets its value written out—otherwise tsc reports Enum member must have initializer.
An enum stays in JavaScript
tsc erases annotations and type declarations. That does not work for an enum: its values are needed at run time. Here is the start of the .js file tsc writes from the first example—Level has become a real object:
"use strict";
var Level;
(function (Level) {
Level[Level["Low"] = 0] = "Low";
Level[Level["Mid"] = 1] = "Mid";
Level[Level["High"] = 2] = "High";
})(Level || (Level = {}));Each line inside the function writes a pair both ways: Level.Mid gives 1, and Level[1] gives "Mid". That is how a numeric enum lets you find a member's name by its value.
Interviewers like to ask this: what is left of an enum after compilation? The answer: an object with the members, and for a numeric enum also the reverse "number → name" pairs.
A literal union instead of an enum
Many teams write a union of literal types instead of an enum: type Level = "low" | "mid" | "high";. It is checked as strictly, but it disappears entirely at compile time and adds not a single line to the .js. An enum is the choice when you need names for numbers or an object with all the values at run time.
Common mistake
A file with an enum is run with node levels.ts:
enum Level { // ✗
Low,
Mid,
High,
}
console.log(Level.Mid);SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only modetsc compiles this file without errors. It is Node that refuses: node file.ts runs in a mode that only erases types, while an enum has to become an object. Compile the file with tsc levels.ts and run node levels.js, or try the code in the TypeScript Playground. node file.ts runs a literal union without any trouble.
Key points
- An
enumgives names to a set of values; numbering starts at0, and a member without a value gets the previous member's value plus one. - In a string
enum, every member gets its value written out. - An
enumis not erased: tsc turns it into an object in the.js, andnode file.tswill not run such a file. - A literal union like
"low" | "mid" | "high"is a common replacement for anenum: it disappears entirely at compile time.