Editor’s note: This guide to TypeScript enums vs. types was last updated by Oyinkansola Awosan on 16 June 2023 to reflect recent changes to TypeScript. This update also includes new sections on extracting the object type of enums and converting enums to arrays. To learn more about enums, check out our guide to extending enums in TypeScript.
TypeScript (as you probably already know) is an open source, strongly typed, object-oriented compiled language developed and maintained by the team at Microsoft. It is a superset of JavaScript with static typing options. It is designed to develop large and scalable applications that compile to JavaScript.
Writing readable code is important. TypeScript types and enums contribute to code readability by providing clear and expressive ways to describe and work with data in TypeScript. In this article, we will be exploring how both work, why, and how to use them.
In most object-oriented programming languages like C, C#, and Java, there is a data type we know as enumerations — enums for short. Java enums are a special kind of Java class used to define collections of constants. JavaScript, however, does not have the enum data type, but they‘ve been available in TypeScript since v2.4.
Enums allow us to define or declare a collection of related values that can be numbers or strings as a set of named constants. Unlike some types available in TypeScript, enums are preprocessed and not tested at compile time or runtime. Enums are defined with the enum keyword, like this:
enum Continents { North_America, South_America, Africa, Asia, Europe, Antartica, Australia } // usage var region = Continents.Africa;
In TypeScript, the type system refers to the various types of values the language accepts. A type refers to a value’s properties and functions; this means that every value in TypeScript has a type. The type system validates the values given before the program stores them.
The TypeScript compiler uses types to analyze your code and hunts for errors and bugs. Types are definitions of data types. This means a type of data is defined by its type. There are different types in TypeScript. Some common ones include:
Enums are just one useful way to organize code in TypeScript. With enums, you can create constants that you can easily relate to, making constants more legible. Enums also allow developers the freedom to create memory-efficient custom constants in JavaScript. As we know, JavaScript does not support enums, but TypeScript helps us access them.
As mentioned, TypeScript enums save runtime and compile time with inline code in JavaScript (which we will see later in this article). And lastly, TypeScript enums also provide a certain flexibility that we previously had only in languages like Java. This flexibility makes it easy to express and document our intentions and use cases easily.
You should use enum types whenever you need to represent a fixed set of constants. That includes enum types such as the planets in our solar system and data sets where you know all possible values at compile time — for example, the choices on a menu, command line flags, etc.
This means that enums should ideally be used in situations where there are distinct values that can be seen as constants, like seven days of the week and the other examples given above:
enum Days { Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }
Enums can be used inside array initializations just as other TypeScript data types. Here’s a quick example:
enum NigerianLanguage { Igbo, Hause, Yoruba } //can be used in array initialisation let citizen = { Name: 'Ugwunna', Age: 75, Language: NigerianLanguage.Igbo }
Enums can also be used where strings or constants need to be represented in a variable.
TypeScript enums can be great to use, but there are scenarios where they are unnecessary. However, some developers tend to overlook these scenarios and go ahead to use them, which is not a great practice. Enums are best suited for representing a fixed set of values.
If you intend to reassign or change the enum member values, it is important to remember that enums are type-safe and, therefore, would return compile errors on reassignment. If you anticipate that the values will need to be dynamically generated at runtime, use constants or other data structures that may be more appropriate.
Enum values are usually defined upfront, and defining and managing a long list of constants within an enum can become cumbersome. As a result of this, enums should be avoided when working with large or open-ended sets of values. Using an enum may not be the best choice if the values to be represented are from an external data source.
Enums are primarily designed for compile-time constants and may not easily synchronize with external data sources. It is important to remember that the general idea behind enums was to help create a user-defined constants system.
TypeScript enums are a way to define a set of named constants. They allow you to create a collection of related values that can be assigned to variables or used as a type. Enums provide a convenient way to work with a fixed set of values in a type-safe manner. By default, enums will initialize the first value to 0 and add 1 to each additional value. There are three types of TypeScript enums, namely:
By default, TypeScript enums are number-based. This means they can store string values as numbers. Numbers, and any other type that is compatible with them, can be assigned to an instance of the enum. Let’s say we want to store the days of the weekend. The representing enum in TypeScript can look something like this:
enum Weekend { Friday, Saturday, Sunday }
In the code block above, we have an enum we call Weekend
. The enum has three values: Friday
, Saturday
, and Sunday
. In TypeScript, like in other languages, enum values start from zero and increase by one for each member. They will be stored like this:
Friday = 0 Saturday = 1 Sunday = 2
We see that enums are always assigned numbers for storage; the value always takes the numeric value of zero, although we can customize the storage values with our own logic.
In TypeScript, we can dictate the first numeric value of our enumerations. Using the weekend days example above, we can initialize the first numeric value like this:
enum Weekend { Friday = 1, Saturday, Sunday }
The above code block will store Friday
as 1
, Saturday
as 2
, and Sunday
as 3
. If we add a number to the first member, we still get sequential incrementation by one for the rest of the members. However, we have the power to dictate that we do not want a sequential trail by giving them any numerical value. The code block below is semantic and works in TypeScript:
enum Weekend { Friday = 1, Saturday = 13, Sunday = 5 }
Just like other data types in TypeScript, we can use enums as function parameters or return types, like this:
enum Weekend { Friday = 1, Saturday, Sunday } function getDate(Day: string): Weekend { if ( Day === 'TGIF') { return Weekend.Friday; } } let DayType: Weekend = getDate('TGIF');
We declared a Weekend
enum above. We then declared a getDate
function that takes the input Day
, which returns a Weekend
enum. In the function, we check for some condition that now returns an enum member.
The value of a numeric enum can either be constant or evaluated, just like any other number data type in TypeScript. You can define or initialize your numeric enum with a computed value:
enum Weekend { Friday = 1, Saturday = getDate('TGIF'), Sunday = Saturday * 40 } function getDate(day : string): number { if (day === 'TGIF') { return 3; } } Weekend.Saturday; // returns 3 Weekend.Sunday; // returns 120
When enums include a mixture of computed and constant members, the enum members that are not initialized either come first or must come after other initialized members with numeric constants. Ignoring this rule above results in an initializer error; remember to rearrange the enum members accordingly if you see that.
If you want to boost the performance of your numeric enums, you can declare them as a constant. Let’s use our weekend example to illustrate:
enum Weekend { Friday = 1, Saturday, Sunday } var day = Weekend.Saturday;
When compiled to JavaScript, the runtime looks up Weekend
and looks up Weekend.Saturday
at execution. For optimal performance at runtime, you can make the enum a constant instead, like this:
const enum Weekend { Friday = 1, Saturday, Sunday } var day = Weekend.Saturday;
The JavaScript generated at compile with the constant is:
var day = 2;
We see how the compiler just inlines the enum usages and does not even bother generating JavaScript for enum declarations when it sees the const
. It is important to be aware of this choice and the consequences that may result when you have use cases that require number-to-strings or strings-to-number lookups. You can also pass the compiler flag preserveConstEnums
, and it will still generate the Weekend
definition.
So far, we have only looked at numeric enums, wherein the member values are numbers. In TypeScript, your enum members can also be string values. String enums are vital and easy to deal with for the purpose of readability during error logging and debugging because of their meaningful string values. Refer to the following code:
enum Weekend { Friday = 'FRIDAY', Saturday = 'SATURDAY', Sunday = 'SUNDAY' }
It can then be used to compare strings in conditional statements like this:
enum Weekend { Friday = 'FRIDAY', Saturday = 'SATURDAY', Sunday ='SUNDAY' } const value = someString as Weekend; if (value === Weekend.Friday || value === Weekend.Sunday){ console.log('You choose a weekend'); console.log(value); }
In the example above, we have defined a string enum, Weekend
, just like the numeric enum we had above, but this time with the enum values as strings. The obvious difference between numeric and string enums is that numeric enum values are mostly sequentially incremented automatically, while string enum values are not incremented; rather, each value is initialized independently.
TypeScript also allows for a mixture of strings and numbers, called heterogeneous enum. In this type of enum, we can assign numeric and string values to its members. This type of enum is used sparingly as it has few applications and is more limited. Refer to this example:
enum Weekend { Friday = 'FRIDAY', Saturday = 1, Sunday = 2 }
Although this is possible, the range of scenarios that will likely require this use case is small. So, unless we are trying to take advantage of JavaScript’s runtime behavior in a clever way, it is advised that we do not use heterogenous enums.
TypeScript enums support reverse mapping, which simply means that just as we have access to the value of an enum member, we also have access to the enum name itself. We’ll use a sample of our first demonstration to portray this:
enum Weekend { Friday = 1, Saturday, Sunday } Weekend.Saturday Weekend["Saturday"]; Weekend[2];
In the code block above, Weekend.Saturday
will return 2
, and then Weekend["Saturday"]
will also return 2
. Interestingly, however, due to reverse mapping, Weekend[2]
will return its member name Saturday
. We can see a simple way TypeScript interprets reverse mapping with a log command:
enum Weekend { Friday = 1, Saturday, Sunday } console.log(Weekend);
If you run this in a console, you will see this output:
{ '1': 'Friday', '2': 'Saturday', '3': 'Sunday', Friday : 1, Saturday : 2, Sunday : 3 }
The objects contain the enums appearing both as values and as names, just as TypeScript intended. This shows the potency of reverse mapping in TypeScript.
To extract the object types of enums in TypeScript, you can use the keyof
operator in combination with the enum itself. Here’s an example:
enum Color { Red = 'RED', Green = 'GREEN', Blue = 'BLUE', } type ColorKey = keyof typeof Color; // 'Red' | 'Green' | 'Blue' type ColorValue = typeof Color[ColorKey]; // 'RED' | 'GREEN' | 'BLUE' const colorKey: ColorKey = 'Red'; const colorValue: ColorValue = Color[colorKey]; console.log(colorKey); // Output: 'Red' console.log(colorValue); // Output: 'RED'
In this example, the keyof typeof Color
expression extracts the keys of the Color
enum: Red
, Green
, and Blue
. The resulting type is ColorKey
, a union type of the keys Red
, Green
, and Blue
.
The typeof Color[ColorKey]
expression accesses the values of the Color
enum using the extracted keys. This results in the type ColorValue
, a union type of RED
, GREEN
, and BLUE
.
You can then use the ColorKey
type to declare variables that can hold enum keys and the ColorValue
type to declare variables that can hold enum values. In the example, colorKey
is assigned the value Red
, which is a valid key of the Color
enum. Similarly, colorValue
is assigned the corresponding value of RED
. The outputs of console.log(colorKey)
and console.log(colorValue)
are Red
and RED
, respectively.
To transform a numeric enum into an array, there are two options available in TypeScript:
Object.values
functionObject.keys
functionUsing the Object.values
function would look like what we have below:
enum Colors { Red, Blue, Yellow } console.log(Object.values(Color).filter((v) => isNaN(Number(v))) );
There are certain use cases where it’s optimal and efficient to use enums. There are also instances when you should put the enums away. Below, we’ll discuss TypeScript enums best practices. When working with a language and its concepts, following the best practices to ensure clean and maintainable code is essential. For TypeScript enums, here are some recommended best practices:
Animal
is preferred over enum animal
By following these best practices, you can ensure that your TypeScript enums are well-defined and maintainable and enhance the overall readability of your code.
Depending on your use case, there are some alternatives to TypeScript enums that you can use. Some of them include the union of string literals types and the as const
. A union of string literals is a type that represents a specific set of string values. It allows you to define a type that can only accept one of the specified string literals, providing a more restrictive and specific type constraint.
Using a union of string literals, TypeScript performs static type checking and ensures that only the specified string literals are allowed for variables of the declared type. This improves type safety and eliminates potential runtime errors caused by assigning invalid values.
The as const
assertion is applicable when you want to ensure that values or expressions are treated as immutable and their types are narrowed down to their literal representations. This can provide additional type safety and enable TypeScript to perform more accurate type inference. as const
lets you generate your types from your data.
We have been able to take a good look at enums in TypeScript, including their types and properties. We also saw the syntax and practical examples of how they are used. We saw other important enums aspects like constants in enums, computed enums, and even reverse mapping.
It is noteworthy that for string enums, reverse mapping is not supported. Also, for the heterogeneous ones, it is only supported for numeric type members but not for string type members. Happy coding!
Further reading:
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "TypeScript enums vs. types: Enhancing code readability"
Good stuff bro