Enumeration in TypeScript
Using TypeScript, comparably to other programming languages, we can define finite numerical (and string-based) sets by using a data structure called enumeration. As I am going to prove later in this article, enumerations fit well together with other TypeScript constructs like namespaces and literals.
Enumeration with numbers
We can create an enumeration type indexed with a number value:
const enum ENumbers {
ONE = 1,
TWO = 2,
THREE = 3,
// ...
}
I applied the const
modifier to the aforementioned snippet to declare ENumbers
— this ensures that the structure leaves no additional JavaScript code during runtime (if for some reasons we want the TypeScript compiler to emit an object with the defined properties, we can leave it without const
). Interestingly enough, we are allowed to merge multiple const enum
statements across our codebase but obviously cannot merge const enum
and regular enum
statements for the same type — this stems from the fact that at its core, enum
declarations behave in a similar fashion to interface
declarations.
We can declare a similar enumeration concept using number literals:
type TNumbers = 1 | 2 | 3; // etc.
Using number literals makes such definition more terse at the expense of losing all the previously defined labels. For what is worth, types never leave any runtime trace as well.
Enumeration with strings
Similarly to numbers, we can define enumeration types using strings, like in the following snippet:
const enum ELetters {
a = 'a',
b = 'b',
c = 'c',
// ...
}
As you might realise, the definition seems to contain complexity which is not required (the label and the value is the same). Another question is whether we should use upper case letters for labels or not.
ELetters
can be reduced to a string literal with ease:
type TLetters = 'a' | 'b' | 'c';
Interoperability of enumerations and namespaces
We can use the name of the enumeration type to define an namespace. We can use such a namespace to e.g. allow a function to belong to a certain type:
enum ECountry {
US = 'US',
UK = 'UK',
DE = 'DE',
FR = 'FR',
}namespace ECountry {
export const getPrice = (country: ECountry): number => {
switch(country) {
case ECountry.US:
case ECountry.UK:
return 10;
default:
return 5;
}
}
}
In the aforedefined example, we defined a function called getPrice
that can be called in a special fashion: ECountry.getPrice(...)
. Naturally, we can leverage this pattern in multitude of different ways, defining other structures — please note though that overusing it might result in a rather unmaintainable codebase.
Interoperability of enumerations and literals
As our enumeration types might hold a lot of additional code which is not related to the very enumeration aspect of them, we might want to abstract them away (or when we want to distribute a codebase as a library and we don’t want to expose enumeration types). This is possible by defining a type literal which values correspond to the values of the enumeration.
Let’s define a type literal for the ECountry
enumeration and let’s define an interface that returns pricing methods:
type TCountry = 'US' | 'UK' | 'DE' | 'FR';interface IPricing {
getPrice: (country: TCountry) => number;
}
Please note that the IPricing.getPrice
function requires a TCountry
type, not ECountry
. Thanks to the structural typing properties of the TypeScript language, we can substitute TCountry
with ECountry
nevertheless:
const getPricing = (): IPricing => ({
getPrice: ECountry.getPrice,
});
This discovery allows us to treat enums as potential vectors for different business logics. One obvious application could be having different enums for production and for testing code. Whether this pattern is viable for a business project or not, I will leave that to the reader to decide.
Summary
As we might have seen, TypeScript allows us to convey enumeration using either enumeration types or literal types and it is possible to use both approached interchangeably. Personably, I prefer using literal types as the structure is way shorter and I get the same type support from the TypeScript compiler as compared to using enumeration types.