Typescript has unions, so are enums redundant?
With the recent versions of TypeScript, it is easy to declare iterable union types. Therefore, you should prefer union types to enums.
How to declare iterable union types
const permissions = ['read', 'write', 'execute'] as const;
type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute'
// you can iterate over permissions
for (const permission of permissions) {
// do something
}
When the actual values of the union type do not describe theirselves very well, you can name them as you do with enums.
// when you use enum
enum Permission {
Read = 'r',
Write = 'w',
Execute = 'x'
}
// union type equivalent
const Permission = {
Read: 'r',
Write: 'w',
Execute: 'x'
} as const;
type Permission = typeof Permission[keyof typeof Permission]; // 'r' | 'w' | 'x'
// of course it's quite easy to iterate over
for (const permission of Object.values(Permission)) {
// do something
}
Do not miss as const
assertion which plays the crucial role in these patterns.
Why it is not good to use enums?
1. Non-const enums do not fit to the concept "a typed superset of JavaScript"
I think this concept is one of the crucial reasons why TypeScript has become so popular among other altJS languages. Non-const enums violate the concept by emitting JavaScript objects that live in runtime with a syntax that is not compatible with JavaScript.
2. Const enums have some pitfalls
Const enums cannot be transpiled with Babel
There are currently two workarounds for this issue: to get rid of const enums manually or with plugin babel-plugin-const-enum
.
Declaring const enums in an ambient context can be problematic
Ambient const enums are not allowed when the --isolatedModules
flag is provided.
A TypeScript team member says that "const enum
on DT really does not make sense" (DT refers to DefinitelyTyped) and "You should use a union type of literals (string or number) instead" of const enums in ambient context.
Const enums under --isolatedModules
flag behave strangely even outside an ambient context
I was surprised to read this comment on GitHub and confirmed that the behavior is still true with TypeScript 3.8.2.
3. Numeric enums are not type safe
You can assign any number to numeric enums.
enum ZeroOrOne {
Zero = 0,
One = 1
}
const zeroOrOne: ZeroOrOne = 2; // no error!!
4. Declaration of string enums can be redundant
We sometimes see this kind of string enums:
enum Day {
Sunday = 'Sunday',
Monday = 'Monday',
Tuesday = 'Tuesday',
Wednesday = 'Wednesday',
Thursday = 'Thursday',
Friday = 'Friday',
Saturday = 'Saturday'
}
I have to admit that there is an enum feature that cannot be achieved by union types
Even if it is obvious from the context that the string value is included in the enum, you cannot assign it to the enum.
enum StringEnum {
Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!
This unifies the style of enum value assignment throughout the code by eliminating the use of string values or string literals. This behavior is not consistent with how TypeScript type system behaves in the other places and is kind of surprising and some people who thought this should be fixed raised issues (this and this), in which it is repeatedly mentioned that the intent of string enums is to provide "opaque" string types: i.e. they can be changed without modifying consumers.
enum Weekend {
Saturday = 'Saturday',
Sunday = 'Sunday'
}
// As this style is forced, you can change the value of
// Weekend.Saturday to 'Sat' without modifying consumers
const weekend: Weekend = Weekend.Saturday;
Note that this "opaqueness" is not perfect as the assignment of enum values to string literal types is not limited.
enum Weekend {
Saturday = 'Saturday',
Sunday = 'Sunday'
}
// The change of the value of Weekend.Saturday to 'Sat'
// results in a compilation error
const saturday: 'Saturday' = Weekend.Saturday;
If you think this "opaque" feature is so valuable that you can accept all the drawbacks I described above in exchange for it, you cannot abandon string enums.
How to eliminate enums from your codebase
With the no-restricted-syntax
rule of ESLint, as described.
As far as I see they are not redundant, due to the very simple reason that union types are purely a compile time concept whereas enums are actually transpiled and end up in the resulting javascript (sample).
This allows you to do some things with enums, that are otherwise impossible with union types (like enumerating the possible enum values)
There are few reasons you might want to use an enum
- You can iterate over an
enum
. - You can use an
enum
asflags
. Bit Flags - Here are some use cases. Enums TypeScript Deep Dive.
I see the big advantages of using a union is that they provide a succinct way to represent a value with multiple types and they are very readable.
let x: number | string
EDIT: As of TypeScript 2.4 Enums now support strings.
enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}