Last active
December 15, 2022 17:54
-
-
Save msichterman/2c94d47bbe932cb1c0b1023a99704896 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//// 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