Fullstack Developer & Whitewater Kayaker & Scout
When to declare enum and when not and why const enums are not so good
I wrote this post as a reminder to myself that using const enums in a library is a really bad idea.
So in short. If you are writing a library and you export a const enum
then
some developers will not be able to compile their applications if they import
your library. Especially when using Babel. Here we look at why.
When you declare an enum
, Typescript will generate some code for it. This is
fine if you are writing an application code and you do not care what is
generated. But when you are writing a library you sometimes do not need the
features provided by this style of declarations - you just want to use enums
instead of constants.
This has a solution - you can use const enum
instead of an enum
. Typescript
does not generate code for const enum
declaration. This is great - it is just
like using constant - but there is a problem.
When you are writing a library you do not have control over your code and how is
used or compiled. Nor what compiler is used and how. Especially Babel and other
transpilers operate over one file at a time. That means that the type
information are stripped and a code transformation that depends on understanding
the full type system cannot be applied. Simply const enum
imported to another
file is gone.
To prevent this error in libraries you should use --isolatedModules
option in
your tsconfig.json
file.
Further reading
enum
and const enum
are features from "the old days" of the Typescript where
the Javascript landscape was a lot different. In addition, enums generate a lot
of code you probably do not want. And also numeric enums are not type-safe. And
instead of key-value-based enums you can use an object which will serve the
same.
const Direction = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
// Get to the const values of any object
type Values<T> = T[keyof T];
// Values<typeof Direction> yields 0 | 1 | 2 | 3
declare function move(direction: Values<typeof Direction>): void;
move(30);
// ^ ๐ฅ This breaks!
move(0);
// ^ ๐ This works!
move(Direction.Left);
// ^ ๐ This also works!
// And now for the Status enum
const Status = {
Admin: 'Admin',
User: 'User',
Moderator: 'Moderator',
} as const;
// Values<typeof Status> yields "Admin" | "User" | "Moderator"
declare function closeThread(
threadId: number,
status: Values<typeof Status>,
): void;
closeThread(10, 'Admin'); // All good!
closeThread(10, Status.User); // enum style
A better approach is to use union types. A simple union type gives you something that works similary and is much more aligned with Typescript.
type Status = 'Admin' | 'User' | 'Moderator';
declare function closeThread(threadId: number, status: Status): void;
closeThread(10, 'Admin');
// All good :-)
Further reading
enum
or const enum
in application code--isolatedModules
in tsconfig.json
to true
in librariesconst enum
does not work in libraries