Today I found that there is a difference between enum
and const
enum
in TypeScript in the generated JavaScript. Namely:
-
with a non-const
enum
the compiler generates an object with key-value pairs. -
with a
const enum
the definition alone doesn't generate any code, only usage of the enum does.
Note: all generated JavaScript code is generated from the TypeScript v4.1 compiler.
non-const enum
definition
Let's say we wanted to rate desserts, we might have a rating scale
defined as an enumeration. Using the enum
construct in TypeScript we
might define something like:
enum DessertRating {
Delicious,
ToDieFor,
Scrumptious,
}
TypeScript generates the following JavaScript:
var DessertRating;
(function (DessertRating) {
DessertRating[DessertRating["Delicious"] = 0] = "Delicious";
DessertRating[DessertRating["ToDieFor"] = 1] = "ToDieFor";
DessertRating[DessertRating["Scrumptious"] = 2] = "Scrumptious";
})(DessertRating || (DessertRating = {}));
const enum
definition
Now we might want to rate our day in our app so we might have a scale
defined as a const enum
for DayRating
like so:
const enum DayRating {
Amazing,
Wonderful,
Engaging,
}
For the const enum
definition case this would yield no JavaScript output.
enum
Usage
Now you might be wondering what do usages of the enum values look like in each case.
const dessertRating = DessertRating.Scrumptious;
This generates the following JavaScript as output:
const dessertRating = DessertRating.Scrumptious;
For the case of const enum
we might write JavaScript like so:
const dayRating = DayRating.Amazing;
Which would generate the following JavaScript:
const dayRating = 0 /* Amazing */;
This is known as inlining.
When can the TypeScript typechecker not inline?
If you need to compute any enum value then you may not be able to
take advantage of the lower bloat benefits of using const enum
in
more places.
For example, suppose we have a configuration file generated by our
deploy process that is imported and a value in there is queried to
generate a derived value for the value of any enum value, then we
would not be able to define it as a const enum
.
Let's say this is the generated config:
{
"currentInstanceSize": "t3.medium"
}
And we try to use the config.currentInstanceSize
as the string value
for one of our enum values like so:
import config from './config';
const enum InstanceSize {
t3_small = 't3.small',
t3_medium = 't3.medium',
t3_large = 't3.large',
current = config.currentInstanceSize,
}
Then the TypeScript typechecker would say something like the following in our error output:
Computed values are not permitted in an enum with string valued members.
Another limitation on const enum
definition are that you cannot do a
dynamic lookup of the value from a string, such as:
const lookup = 'm3_small';
const instanceSize = InstanceSize[lookup];
This yields the typechecker error of:
A const enum member can only be accessed using a string literal.
Yay for type errors letting us know when we can't do something instead of waiting until runtime for something to blow up in unpredicatable ways.
What is an ambient enum
?
Ambient enums are those that have the declare
keyword prefixed to
the definition. These are used to define the shape of an existing enum
type.
This can look like the following:
declare enum DOW {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
There are other differences and limitations that are enumerated (I couldn't help myself) here: TypeScript Handbook: Enums