Typescript's enums may not be what you think

gingerchew

ginger

Posted on November 8, 2023

Typescript's enums may not be what you think

If you're not familiar with what an enum looks like, here you go:

enum Direction {
  Up = 1,
  Down, // 2
  Left, // 3
  Right, // 4
}
Enter fullscreen mode Exit fullscreen mode
References
<p>
    <a href="https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings">Enums - TypeScript Handbook</a>
</p>
Enter fullscreen mode Exit fullscreen mode

They act as a collection of constants. This can be useful, especially if you are trying to have incredibly unique keys:

enum DirectiveKeys {
    Skip = '__c_skip_me_',
    Remove = '__c_remove_me_',
    Add = '__c_add_me_'
}

const objectThatShouldBeSkipped = {
    action: DirectiveKeys.Skip
}
Enter fullscreen mode Exit fullscreen mode

Reasons to use them

When using an enum, it locks down the constants while keeping them in a manageable object format:

const DirectiveKeys = {
    Skip: '__c_skip_me_',
    Remove: '__c_remove_me_',
    Add: '__c_add_me_'
}

DirectiveKeys.Skip = 'Whoops, this is still mutable';

const objectThatShouldBeSkipped = {
    action: DirectiveKeys.Skip // will never be skipped
}
Enter fullscreen mode Exit fullscreen mode

When using enum, you are also able to have implied values:

enum ExplicitValues {
    Up = 0,
    Down = 1,
    Right = 2,
    Left = 3
}
Enter fullscreen mode Exit fullscreen mode

Is the same as:

enum ImplicitValues {
    Up,
    Down,
    Right,
    Left
}
Enter fullscreen mode Exit fullscreen mode

Reasons to not use them

They aren't abstracted away. That's right, TypeScript enums are compiled into your code. You can easily say "Well, whatever. It's TypeScript, they know what they're doing." But what they are doing is converting this:

enum Const {
    Up,
    Down,
    Right,
    Left
}
Enter fullscreen mode Exit fullscreen mode

Into this:

var Const;
(function (Const) {
    Const[Const["Up"] = 0] = "Up";
    Const[Const["Down"] = 1] = "Down";
    Const[Const["Right"] = 2] = "Right";
    Const[Const["Left"] = 3] = "Left";
})(Const || (Const = {}));
Enter fullscreen mode Exit fullscreen mode

As someone who is a fan of smaller packages, this was a very frustrating bit of code to find. Converting the enum into an object/const saved hundreds of bytes. Why?

For each enum, there was a snippet of JavaScript like the one above to match.

The generated JavaScript only prevents mutation if TypeScript is present. Otherwise it can be easily overwritten, a main selling point of the enum type.

Here is a vanilla way to generate an enum using a Proxy:

const Enum = (enums) => {
    Object.entries(enums).forEach((enums, [ key, value ]) => {
        enums[value] = key;
        return enums;
    })
    return new Proxy(enums, {
        get(target, key) {
            return target[key];
        },
        removeProperty: () => false,
        set: () => false,
    })
}
Enter fullscreen mode Exit fullscreen mode

This disables the ability to add or remove properties, as well as generating the alternating key value that the TypeScript enum generates.

Another benefit? It's reusable, and doesn't create multiple copies of the same code!

const Directions = Enum({
    Up: 1,
    Down: 2,
    Right: 3,
    Left: 4
});

const Compass = Enum([
    "North",
    "East",
    "South",
    "West"
]);
Enter fullscreen mode Exit fullscreen mode

Wow, production ready code for anyone to use, sweet!

Now hang on.

The difference between my snippet and the code generated by TypeScript is that mine takes advantage of ES6+ features like Proxy. If your target audience doesn't include that, my condolences.

My snippet also doesn't come with the backing of Microsoft and the TypeScript team, meaning that it isn't as battle tested.

The final and most important reason to use the TypeScript enum? They have all the Intellisense benefits. Maybe one day I will work on a type that gives my little function all the same Intellisense goodies.

Until then, do what you want.

Post Script

I think that the code that is generated could probably be updated a little. Like, using an arrow function should be fine.

var Const;
((Const) => {
    Const[Const["Up"] = 0] = "Up";
    Const[Const["Down"] = 1] = "Down";
    Const[Const["Right"] = 2] = "Right";
    Const[Const["Left"] = 3] = "Left";
})(Const || (Const = {}));
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
gingerchew
ginger

Posted on November 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related