Capítulo 20 de 36
Enums are one of the few TypeScript features that add actual runtime behavior on top of JavaScript (not just erasable type syntax) — a named-constant construct with numeric and string variants, real runtime objects, and (for the literal-initialized case) special type-system integration that turns members into their own literal types.
enum Direction { Up = 1, Down, Left, Right } auto-increments from the first initializer (Down = 2, Left = 3, Right = 4). Omitting all initializers starts from 0. Auto-increment rule for uninitialized members: a member with no initializer is only allowed as the first member, or immediately after a numeric-constant member — an uninitialized member can't legally follow a computed member (TS can't infer "one more than an expression it can't evaluate at compile time").0), follows a numeric-constant member with no initializer (→ previous + 1), or is initialized with a constant enum expression (literals, references to other constant enum members, parenthesized/unary/binary combinations of those — evaluable entirely at compile time). Anything else (e.g. "123".length) is computed. Enums can freely mix constant and computed members, as long as ordering rules for uninitialized members are respected.0/n+1 cases), two special things happen: (1) each member becomes its own literal type, so an interface field can be typed to accept only one specific member (kind: ShapeKind.Circle), catching a wrong-member assignment as a type error; (2) the enum's own type becomes the union of all its member types, letting TypeScript reason exhaustively about comparisons — e.g. flagging a logically-impossible x !== E.Foo || x !== E.Bar check as an error, since a two-member enum value failing the first !== can only be E.Foo, making the second comparison always true.f(E) where f expects { X: number } works).keyof on an enum doesn't do what you'd expect: use keyof typeof EnumName (not keyof EnumName) to get a union of the enum's key names as string literals — EnumName alone refers to the enum's value type (the union of members), while typeof EnumName refers to the type of the runtime object, whose keyof is the key-name union.Enum.A (name → value) and Enum[value] (value → name) work. String enums do not get a reverse mapping at all.const enum: fully inlined at every use site during compilation, generating zero enum object code — cheaper at runtime, but restricted to constant-expression members only (no computed members, since there's nothing left to inline from at each call site once the object itself is erased).const enum pitfalls (ambient/cross-project use): publishing a const enum in a .d.ts for other projects to consume is risky — it's fundamentally incompatible with isolatedModules (each file must be independently compilable, but inlining requires knowing the enum's actual values, which single-file compilation can't guarantee); it can silently inline values from one dependency version while a different version is actually installed at runtime, producing values-mismatch bugs that automated tests often miss (since tests usually run against the same build the value was inlined from). Two mitigations: avoid const enum in code meant to be published as a dependency (a project inlining its own enums has no such cross-version risk), or use preserveConstEnums to keep the object-based emit while still allowing local const usage, then strip const from the published .d.ts.declare enum X {...}): describe an enum whose implementation already exists elsewhere (types only, no emit). One subtlety: in an ambient (non-const) enum, an uninitialized member is always treated as computed, unlike a regular enum where it can be constant if the preceding member is.as const as an enum alternative: { Up: 0, Down: 1 } as const gets most of an enum's benefits (named constants, works as a type via typeof Obj[keyof typeof Obj]) while staying closer to plain JavaScript — favored by teams wanting to avoid TS's non-standard runtime-emitting enum construct, especially given a JS-native enum proposal exists that could eventually diverge from TS's own semantics.enum ShapeKind { Circle, Square }
interface Circle { kind: ShapeKind.Circle; radius: number }
const c: Circle = { kind: ShapeKind.Square, radius: 100 }; // Error: wrong enum member type
enum LogLevel { ERROR, WARN, INFO, DEBUG }
type LogLevelStrings = keyof typeof LogLevel; // "ERROR" | "WARN" | "INFO" | "DEBUG"
keyof typeof idiom for getting an enum's key names as a string literal union — plain keyof LogLevel would not give this result.| Enum kind | Auto-increment | Reverse mapping | Runtime emit |
|---|---|---|---|
| Numeric | yes | yes | plain object |
| String | no | no | plain object |
const enum | (numeric-style, but constant-only) | n/a — fully inlined | none (values inlined at call sites) |
Ambient (declare enum) | uninitialized members always "computed" | depends on underlying impl | none (types only) |
keyof typeof EnumName, never keyof EnumName, to get an enum's key names as a type.const enum in anything published as a .d.ts dependency for other projects — the cross-version inlining mismatch is a real, hard-to-catch bug class; preserveConstEnums or plain enums are safer for published code.as const object literals as a lighter-weight, more JS-native alternative to enum when you don't need the runtime enum object itself.keyof typeof pattern used to extract enum key names.