Exploring TypeScript Enums and Their Alternatives
Written on
Chapter 1: Understanding TypeScript Enums
In TypeScript, enums, or enumerated types, are unique data types that can hold a set of constants. While the idea of enums isn’t exclusive to TypeScript, their ability to compile into JavaScript brings them into focus within the language. In this concise article, we’ll explore how enums function when transformed into JavaScript, discuss the inconsistencies found in the compiled output, and examine a more reliable alternative.
Let's dive into the basics of TypeScript Enums.
Section 1.1: Basic Enum Definition
The most straightforward way to define an enum in TypeScript is as follows:
enum Protocol {
HTTP,
HTTPS,
WS
}
When this code is executed, TypeScript assigns numeric values to these enumerated constants automatically, starting from 0. A significant advantage of enums is their use as a type. This allows us to leverage these values for type safety during compile time, which also persists during runtime—unlike traditional TypeScript types that get removed in the compiled output.
We can utilize enums in functions based on their values, demonstrated below:
function printProtocol(protocol: Protocol): void {
switch (protocol) {
case Protocol.HTTP:
console.log("web insecure");
break;
case Protocol.HTTPS:
console.log("web secure");
break;
case Protocol.WS:
console.log("socket protocol");
break;
default:
console.log("Unknown protocol");}
}
printProtocol(Protocol.HTTP);
Section 1.2: The Enums Challenge
JavaScript itself does not currently support enums. Although there’s a TC39 proposal in the works, it isn’t part of the core language yet. Consequently, when TypeScript code is compiled into JavaScript, enums convert into bidirectionally accessible objects, like so:
{
0: "HTTP",
1: "HTTPS",
2: "WS",
HTTP: 0,
HTTPS: 1,
WS: 2
}
This transformation results in a convoluted structure, providing multiple access points for the same data. The situation becomes even more complex when string values are assigned to an enum.
Subsection 1.2.1: Enums with String Values
Let’s take a look at assigning string values to our previous enum:
enum Protocol {
HTTP = "http",
HTTPS = "https",
WS = "websocket"
}
This would compile into a cleaner JavaScript object:
{
HTTP: "http",
HTTPS: "https",
WS: "websocket",
}
While this is neater, the differences in output based on the value types can lead to inconsistencies depending on the use case.
Chapter 2: A Reliable Alternative
To produce more consistent compiled JavaScript, we can use the as const keyword, known as a const assertion. This approach yields a consistent output regardless of the value types assigned. Here’s how this can be achieved:
const Protocol = {
HTTP: 0,
HTTPS: 1,
WS: 2
} as const;
This method prevents the creation of a bidirectional object during compilation, resulting in a consistent output object like:
{
HTTP: 0,
HTTPS: 1,
WS: 2
}
Section 2.1: Deriving Types from Const Assertions
While this solution is effective, we can no longer directly use Protocol as a type. Attempting to do so leads to a compiler error:
'Protocol' refers to a value, but is being used as a type here.
Did you mean 'typeof Protocol'?
However, TypeScript allows us to derive specific types from the const assertion:
const Protocol = {
HTTP: 0,
HTTPS: 1,
WS: 2
} as const;
type Protocol = keyof typeof Protocol;
type ProtocolValues = typeof Protocol[keyof typeof Protocol];
function printProtocol(protocol: ProtocolValues): void {
switch (protocol) {
case Protocol.HTTP:
console.log("web insecure");
break;
case Protocol.HTTPS:
console.log("web secure");
break;
case Protocol.WS:
console.log("socket protocol");
break;
default:
console.log("Unknown protocol");}
}
printProtocol(Protocol.HTTP);
In this case, the Protocol type can hold the values "HTTP", "HTTPS", or "WS", while ProtocolValues can be 0, 1, or 2. This allows us to narrow down types while maintaining runtime logic. Furthermore, we can assign string values as shown below:
const Protocol = {
HTTP: "http",
HTTPS: "https",
WS: "websocket"
} as const;
Now, the ProtocolValues type would be updated to "http", "https", or "websocket".
Subsection 2.1.1: Mixed Data Types in Const Assertions
Interestingly, we can also mix data types for our constant values:
const Protocol = {
HTTP: "http",
HTTPS: 2,
WS: "websocket"
} as const;
This would update ProtocolValues to the narrowed-down type of "http", 2, or "websocket".
The first video provides an in-depth look at TypeScript enums and covers essential front-end interview questions regarding their usage.
The second video walks through a detailed lesson on TypeScript enumerations, focusing on practical examples and applications.
Conclusion
In this article, we explored TypeScript enums, their JavaScript compilation, and an alternative approach known as const assertions. This method not only simplifies refactoring but also maintains type inference, allowing for a consistent compiled output. By leveraging these techniques, we can enjoy the benefits of enums without facing the issues of inconsistent code.
If you're passionate about software engineering and eager to explore innovative ways to achieve your programming goals, follow me for more engaging and practical tech insights.
Thank you for reading, and I look forward to connecting with you in my next article!
Cheers :)