Skip to content

Instantly share code, notes, and snippets.

@msichterman
Last active December 15, 2022 17:54
Show Gist options
  • Save msichterman/2c94d47bbe932cb1c0b1023a99704896 to your computer and use it in GitHub Desktop.
Save msichterman/2c94d47bbe932cb1c0b1023a99704896 to your computer and use it in GitHub Desktop.
//// TYPESCRIPT ENUMS EXPLAINED
// Outcome of Matt Pocock's video titled "Enums considered harmful" – https://youtu.be/YmxwicpROps
// Follow along with the TypeScript Playground: https://url.msich.dev/ts-enums-pg
/* Challenges of TypeScript built-in enums
- Enums are not native to JavaScript
- Enums behave a little bit unpredictibly at runtime
- Const enums have a lot of pitfalls
*/
//// Regular Enums
enum LogLevelEnum {
DEBUG,
WARNING,
ERROR
}
// JS Output. Weird!
const LOG_LEVEL_ENUM = {
DEBUG: 0,
0: 'DEBUG',
WARNING: 1,
1: 'WARNING',
ERROR: 2,
2: 'ERROR'
}
// Solution? String enum.
enum LogLevelStringEnum {
DEBUG = 'DEBUG',
WARNING = 'WARNING',
ERROR = 'ERROR'
}
// Example
function logWithStringEnum(message: string, level: LogLevelStringEnum) {}
logWithStringEnum('String enum test', LogLevelStringEnum.DEBUG)
logWithStringEnum('String enum test', 'DEBUG') // Argument of type '"DEBUG"' is not assignable to parameter of type 'LogLevelStringEnum'
/*
Positives:
- Easy refactors -- change members around and it works great with VSCode
Negatives:
- Can't call with a member of the enum that isn't expressed explicitly as the enum (ex. 'DEBUG')
- TypeScript is a structural type checker -- it shouldn't care as long as a compatible value is passed in
- But... TypeScript is a nominal type checker for enums
*/
enum LogLevel2StringEnum {
DEBUG = 'DEBUG',
WARNING = 'WARNING',
ERROR = 'ERROR'
}
logWithStringEnum('String enum test', LogLevel2StringEnum.DEBUG) // Argument of type 'LogLevel2StringEnum.DEBUG' is not assignable to parameter of type 'LogLevelStringEnum'.
//// Const Enum
const enum LogLevelConstStringEnum {
DEBUG = 'DEBUG',
WARNING = 'WARNING',
ERROR = 'ERROR'
}
// Example
function logWithConstStringEnum(message: string, level: LogLevelConstStringEnum) {}
logWithConstStringEnum('String enum test', LogLevelConstStringEnum.DEBUG)
/*
Positives:
- Enum disappears at runtime and only exists in TypeScript at the type level
- Is then added inline (as a comment) when you use it (see the .JS output)
Negatives:
- Lots of pitfalls detailed here https://www.typescriptlang.org/docs/handbook/enums.html#const-enum-pitfalls
- Never use inside library code, dangerous if you don't control the compilers that is omitting them
*/
//// "POJO" (Plain Old Javascript Object) – The best way to handle enums in TypeScript 🧐
const LOG_LEVEL = {
DEBUG: 'DEBUG',
WARNING: 'WARNING',
ERROR: 'ERROR'
} as const;
type ObjectValues<T> = T[keyof T];
type LogLevel = ObjectValues<typeof LOG_LEVEL>
function log(message: string, level: LogLevel) {
console.log(`${LOG_LEVEL[level]}: ${message}`)
}
log('POJO Test', LOG_LEVEL.DEBUG)
log('Regular String Test', 'DEBUG')
log('String Enum Test', LogLevelStringEnum.DEBUG)
log('Const String Enum Test', LogLevelConstStringEnum.DEBUG)
/*
Positives:
- Feels more natural -- don't always need to import enum
- `as const` lets you be super flexible, with types extracted from keys (type flexible)
Negatives:
- Refactors can be slightly more difficult, since the `LogLevel` const accepts more inputs (see examples above)
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment