But there’s one special type that we want to discuss today and that is enums. Enum, short for Enumerated Type, is a common language feature of many statically types languages such as C, C#, Java, Swift any many others, is a group of named constant values that you can use within your code.
Let’s create an enum in TypeScript to represent the days of the week:
The enum is denoted using the enum keyword followed by the name of the enum (DayOfWeek) and then we define the constant values that we want to make available for the enum.
We could then create a function to determine if it’s the weekend and have the argument that enum:
And finally call it like so:
This is a nice way to remove the use of magic values within a codebase since we have a type-safe representation options that are all related together. But things may not always be as they seem, what do you think you get if you pass this through the TypeScript compiler?
It might surprise you to know that this is valid TypeScript and the compiler will happily take it for you.
Why Did This Happen
And if we output it to the console:
So, therefore we can pass in numbers to a function that expects an enum, the enum itself is both a number and a defined constant.
When This Is Useful
You might be thinking to yourself that this doesn’t seem particularly useful as it really breaks the whole type safe aspect of TypeScript if you can pass in an arbitrary number to a function expecting an enum, so why is it useful?
Let’s say you have a service which returns a JSON payload when called and you want to model a property of that service as an enum value. In your database you may have this value stored as a number but by defining it as a TypeScript enum we can cast it properly:
This explicit cast that’s being done during assignment will turn the day variable from a number to our enum, meaning that we can get a bit more of an understanding of what it represents when it’s being passed around our codebase.
Controlling an Enums Number
Since an enum’s member’s number is defined based on the order in which they appear in the enum definition it can be a little opaque as to what the value will be until you inspect the generated code, but that’s something we can control:
Here’s a new enum that models the state a file could be in, it could be in read or write mode and we’ve explicitly defined the value that corresponds with that mode (I’ve just made up these values, but it could be something coming from our file system).
Now it is clear what values are valid for this enum as we’ve done that explicitly.
But there’s another reason that this can be useful, and that’s using enums for bit flags. Let’s take our
FileState enum from above and add a new state for the file,
Then assuming we have a function that takes the enum we can write code such as this:
Notice how we’re using the
| operator on the
FileState enum and this allows us to perform a bitwise operation on them to create a new enum value, in this case it’ll create 3, which is the value of the ReadWrite state. In fact, we can write this in a clearer way:
Now the ReadWrite member isn’t a hand-coded constant, it’s clear that it’s made up as a bitwise operation of other members of the enum.
We do have to be careful with using enums this way though, take the following enum:
If we were to receive the enum value
E (or 5), is that the result of a bitwise operation of
Foo.A | Foo.D or Foo.B | Foo.C? So, if there’s an expectation that we are using bitwise enums like this we want to ensure that it will be really obvious how we arrived at that value.
We’ve seen that an enum will have a numeric value assigned to it by default or we can explicitly do it on all of them, but we can also do it on a subset of them:
Initially, the values are defined using their position in the index with Sunday through Tuesday being 0 to 2, then when we “reset” the order at Wednesday everything after that is incremented from the new starting position.
This can become problematic if we were to do something like this:
Uh oh, looks like there might be an issue, 2 is both Tuesday and Thursday! If this was a value coming from a data source of some sort, we have some ambiguity in our application. So, if we are going to be setting value it’s better to set all of the values so that it is obvious what they are.
So far, we’ve only discussed enums that are numeric or explicitly assigning numbers to enum values, but an enum doesn’t have to be a number value, it can be anything constant or computed value:
Here we’ve made a string enum, and the generated code is a lot different:
Now we’ll no longer be able to pass in a number to the
isItTheWeekend function, since the enum is not numeric, but we also can’t pass in an arbitrary string, since the enum knows what string values are valid.
This does introduce another issue though; we can no longer do this:
The string isn't directly assignable to the enum type, instead we have to do an explicit cast:
And this can have an impact on how we consume values that are to be used as an enum.
But why stop at strings? In fact, we can mix and match the values of enums within an enum itself:
Provided that all assignable values are of the same type (numeric in this case) we can generate those numbers in a bunch of different ways, including computed values, but if they are all constants, we can mix types to make a heterogeneous enum:
This is quite confusing and can make it difficult to understand how the data works behind the enum, so it’s recommended that you don’t use heterogeneous enums unless you’re really sure it’s what you need.
Disclaimer: this blog post was originally written for LogRocket.