String valued members in enums by ahejlsberg · Pull Request #15486 · microsoft/TypeScript
Enum types in TypeScript come in two flavors: Numeric enum types and literal enum types. An enum type where each member has no initializer or an initializer that specififes a numeric literal, a string literal, or a single identifier naming another member in the enum type is classified as a literal enum type. An enum type that doesn't adhere to this pattern (e.g. because it has computed member values) is classified as a numeric enum type.
With this PR we implement the ability for literal enum types to have string valued members.
enum Color { Red = "red", Green = "green", Blue = "blue" }
The above declaration creates an enum object Color with three members Red, Green, and Blue. The members have corresponding new string literal enum types, Color.Red, Color.Green, and Color.Blue that are subtypes of the string literal types "red", "green", and "blue" respectively. Finally, the declaration introduces a type Color that is an alias for the union type Color.Red | Color.Green | Color.Blue.
When a string literal enum type is inferred for a mutable location, it is widened to its corresponding literal enum type (rather than being widened to type string).
const c1 = Color.Red; // Type Color.Red let c2 = Color.Red; // Type Color
The above behavior exactly corresponds to the behavior for numeric literal enum types, only the values are strings instead of numbers. In fact, an enum literal type can contain any mix of numeric literal values and string literal values.
enum Mixed { A, B, C = "hi", D = 10, E, F = "bye" }
The Mixed type above is an alias for Mixed.A | Mixed.B | Mixed.C | Mixed.D | Mixed.E | Mixed.F, which is a subtype of 0 | 1 | "hi" | 10 | 11 | "bye".
Enum members with string literal values must always specify an initializer, but enum members with numeric literal values may omit the initializer provided it is the first member or the preceding member has a numeric value.
In the code emitted for an enum declaration, string valued members have no reverse mapping. For example, the following code is emitted for the Mixed enum declaration above:
var Mixed; (function (Mixed) { Mixed[Mixed["A"] = 0] = "A"; Mixed[Mixed["B"] = 1] = "B"; Mixed["C"] = "hi"; Mixed[Mixed["D"] = 10] = "D"; Mixed[Mixed["E"] = 11] = "E"; Mixed["F"] = "bye"; })(Mixed || (Mixed = {}));
As is already the case, no code is generated for a const enum. Instead, the literal values of the enum members are inlined where they're referenced.