enum, const enum, as const - Enumeration in TypeScript
Finding Ways to Enumerating Data
In a recent project rewrite, many states need management. To avoid hard-coded values and ensure clarity, I document some findings. For example, there is currently a warning level data:
If I need to use or receive this data in different parts of the project, how can I ensure that I rely on a single source of truth for this data?
Using Enumerations enum
Since this is a TypeScript project, my first thought was to use enumerations (Enums) to manage the data. Enums are a special “non-type-level” feature of TS used to represent a set of constants (immutable values). There is some strange magic involved that not everyone likes, for example, a simple enum:
The actual compiled output will look like this:
This results in an object that looks like this:
The above is Numeric Enums, so usually, String Enums are used:
This means that the Enums type is unique; even if there is another LogLevel defined with the same type, it will still be considered a different type. This syntax enhances the typically Structural Type characteristics of TS to a Nominal Type system characteristic. Behind the scenes, TS uses some magical abstraction but is essentially wrapping a JS object.
- I don’t really like some of the unpredictable magical transformations in TS (if values are not defined, Numeric Enums will be created, which is usually undesirable).
- The characteristics of the type system are slightly different, but I don’t think it’s a big issue.
- The TypeScript Team discussed that if they could do it over, they probably wouldn’t add this feature
Using const enum
This approach allows TypeScript to only handle the values of the enumeration in terms of types, meaning no JS will be generated after compilation. This sounds clean and intuitive! TS will directly replace the places where const enum
is used with the corresponding values at compile time. However, in the TS official documentation, it is generally not recommended to use const enum
, as using it in a shared codebase can lead to issues if the compiler cannot be controlled.
Using as const
After going around in circles, it turns out the best method is right at our feet?? Introducing the old JS object POJO (Plain Old JavaScript Object), which is essentially a JS object that uses as const
to inform TS that this object is completely immutable, similar to Object.freeze(), but in a deep sense and not existing at runtime, truly immutable values.
If you’re not familiar with JS characteristics, you might think that JS’s const
means declaring a variable whose content cannot be changed, but in reality, the immutability here means that the variable cannot be assigned a new memory address. Because of this characteristic, TS cannot determine whether a variable declared with const
is an immutable value.
But when we use as const
in TS, it truly lets TS know that it is immutable, allowing us to obtain a more precise type:
Great! This means we can use keyof
and typeof
to obtain the type of this object and apply it anywhere:
We can even create a utility type to help us with the conversion:
Choices
The arguments against using Enum can be summarized as follows:
- The results after compilation can be a bit strange, and these strange points require special observation of the compilation results or reading the documentation to understand.
- Enums are more rigid in usage (must pass in Enum as a value, compared to
as const
, which only requires passing in the corresponding value).
However, I believe Enums also have their advantages:
- Enum names and purposes are very clear.
- Very rigid, all values can only be input through Enum, ensuring data correctness.
Using as const
entirely to achieve enumeration data management is more intuitive, with less cognitive burden, and is more flexible.
Further Reading
- Enums - TypeScript
- Enums considered harmful - Matt Pocock
- The TRUTH About TypeScript Enums - James Q Quick
- as const: the most underrated TypeScript feature - Matt Pocock
- TypeScript Enums are TERRIBLE. Here’s Why. - Michigan TypeScript