Writing readable code with TypeScript enums

March 13, 2019 0 Comments

Writing readable code with TypeScript enums

 

 

TL;DR: In this article, we will look into enums, what they are and how they are used in TypeScript. We will also use sample code to illustrate the points made.

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 for the development of large and scalable applications that compiles to JavaScript.

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 are, fortunately, now available in TypeScript since version 2.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.

Here are some reasons why enums are very useful in TypeScript:

  • With enums you can create constants that you can easily relate to, making constants more legible
  • With TypeScript enums, developers have the freedom to create memory-efficient custom constants in JavaScript. JavaScript, as we know, does not support enums but TypeScript helps us access them
  • TypeScript enums save runtime and compile time with inline code in JavaScript, (which we will see later in this article)
  • 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.

Enums are defined with the enum keyword like this:

There are three types of TypeScript enums namely:

  1. Numeric enums
  2. String enums
  3. Heterogenous enums

By default, TypeScript enums are number based. This means that they can store string values as numbers. Numbers and any other type that is compatible with it can be assigned to an instance of the enum. Let’s say we want to store days in the weekend. The representing enum in TypeScript can look something like this:

In the code block above, we have an enum we call Weekend. The enum has three values namely: Friday, Saturday and Sunday. In TypeScript, just like in some other languages, enum values start from zero and increase by one for each member. They will be stored like this:

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 are allowed to dictate the first numeric value of our enumerations. Using the weekend days example above, we can initialize the first numeric value like this:

The above code block will store Friday as 1, Saturday as 2 and Sundayas 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:

Just like other data types in TypeScript, we can use enums as function parameters or return types, like this:

In the code block above, we declared a Weekend enum. We then declared a getDate function that takes the input Day that returns a Weekendenum. In the function, we check for some condition that now returns an enum member.

So far we have only looked at enums where 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.

It can then be used to compare strings in conditional statements like this:

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 but rather each value is initialized independently.

TypeScript also allows for a mixture of both strings and numbers, called heterogeneous enum values:

Although this is possible, the range of scenarios that will likely require this use case is really small. So unless you are really trying to take advantage of JavaScript’s runtime behavior in a clever way, it is advised that you do not use heterogenous enums.

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:

Rule #1 — 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 initialised members with numeric constants.

Ignoring this rule above gives an initializer error — if you see that, remember to rearrange the enum members accordingly.

If you want to boost the performance of your numeric enums you can declare them as a constant. Let us use our weekend example to illustrate:

When compiled to JavaScript, at execution the runtime looks up Weekend and looks up Weekend.Saturday. For optimal performance at runtime, you can make the enum a constant instead, like this:

the JavaScript generated at compile with the constant is thus:

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 when you have use cases that will require number to strings or strings to number lookups. You can also pass the compiler flag — preserveConstEnums and it will still generate the Weekenddefinition.

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. A sample of our first demonstration is used to portray this below:

In the code block above, Weekend.Saturday will return 2 and then Weekend["Saturday"] will also return 2 but interestingly, due to reverse mapping Weekend[2] will return its member name Saturday. This is because of reverse mapping. We can see a simple way TypeScript interprets reverse mapping with a log command:

If you run this in a console, you will see this output:

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.

There are places and suitable use cases where it’s optimal and very efficient to use enums

  • Enums can be used inside array initialisations just as other TypeScript data types

Here is a quick example:

  • Enums should ideally be used in situations where there are distinct values that can be seen as constants, like seven days of the week:
  • Enums can also be used in places where strings or constants need to be represented in a variable.

TypeScript enums are not to be used in the following places:

  • When you plan to re-assign or change the enum member values, enums are type-safe and therefore would return compile errors on re-assignment
  • When you want to record dynamic values, enums are best suited for finite items and the general idea behind it was to help create a user-defined constants system
  • Enums cannot be used as variables, doing so would return errors

We have been able to take a good look at enums in TypeScript, 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!

LogRocket is a frontend logging tool 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 apps.

Try it for free.


Tag cloud