Editor’s note: This article was last updated by Oyinkansola Awosan on 12 November 2024 to cover converting strings to enums and to compare TypeScript enums and constants.
TypeScript enums are a special feature that allows you to define a collection of constant values. They typically make code more readable and are particularly useful for representing a fixed set of values, such as directions, status codes, or user roles. They provide a way to give more descriptive names to sets of related values, making your code more intuitive.
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 so:
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. Type defines the structure of data; it also 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. They allow you to specify the expected data types for variables, function parameters, and return values. This strict typing helps catch potential errors early in the development process and improves code quality. Unlike enums, types focus on defining what data should look like rather than assigning specific values.
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 use them anyway, 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’s 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 zero and add one 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
and 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 this:
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 doesn’t even bother generating JavaScript for enum declarations when it sees the const
. It’s 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’s advised that we don’t use heterogeneous 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.
There is no explicitly direct way to convert strings to enums as such converting a string to an enum is not straightforward. However, there are multiple ways to convert a string to an enum and we will be exploring two effective methods:
This method involves using a lookup object to create a mapping between strings and their corresponding enum members, allowing for easy and efficient conversion from strings:
enum Children { First = "Tirenii", Second = "Anita", Third = "Oluwaseun", } const enumLookup: { [key: string]: Children } = { First: Children.First, Second: Children.Second, Third: Children.Third, }; function convertStringToEnum(value: string): Children | undefined { return enumLookup[value]; } console.log(convertStringToEnum("First")); console.log(convertStringToEnum("Second"));
In the code above, we define an enum Children
with string values representing child names. The convertStringToEnum
function uses a lookup object, enumLookup
, to map a string input e.g., First
to the corresponding Children
enum value like Children.First
. The function returns the associated enum value or undefined
if the string is not found in the lookup object.
Reverse mapping, which we covered earlier, is an effective way of converting strings to enums as it maps enum values back to their corresponding keys:
enum Cars { Honda = 9, Toyota = 6, Kia = 4, } function stringToEnum(value: string): Cars | undefined { const key = Object.keys(Cars).find((key) => key === value); if (key) { return Cars[key as keyof typeof Cars]; } return undefined; } console.log(stringToEnum("Honda")); console.log(stringToEnum("Toyota")); console.log(stringToEnum("Kia"));
In the code above, we define an enum Cars
with numeric values assigned to the car brands. The stringToEnum
function uses reverse mapping to convert a string representation of a car brand to its enum value. It iterates through the keys of the Cars
enum, finds the matching key based on the input string and then returns the associated enum value.
If no match is found, it returns undefined
. If one is found, it returns the assigned value.
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:
num 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 the 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:
as const
assertion: 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 dataConstants in TypeScript are variables with unchangeable values that are generally used within an object or array using const declarations. They enhance code readability and are declared by using const
.
Choosing between enums and constants depends largely on your specific needs. Enums are ideal when you require a structured grouping of related values, especially when their meaning is crucial to the application. On the other hand, constants are better suited for lightweight and flexible use cases where runtime efficiency is a priority:
TypeScript enums | Constants | |
---|---|---|
Runtime behavior | Transpiles into a JavaScript object, which adds some runtime overhead due to object lookups | Directly translates to JavaScript, with no added runtime behavior hence it is faster with no object lookups |
Flexibility | Limited flexibility compared to constants because they are rigid once defined | Highly flexible and can be assigned any data type, providing more flexibility in defining values |
Use cases | When structure is important, TypeScript enums are ideal because they represent a fixed set of related values and ensure type safety. Group related values semantically under a single name | When runtime efficiency, immutability, and compatibility with JavaScript features are priorities. Lightweight representation of fixed values. Constants are also the best when there is a need for reusable and configuration values |
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.
Would you be interested in joining LogRocket's developer community?
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 nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
One Reply to "TypeScript enums vs. types: Enhancing code readability"
Good stuff bro