There are numerous videos on trashing TypeScript enums. Essentially if you use enums you must be dumb, duh! See some examples below:
Most enums haters hate enums in their numerical form and I agree they have some footguns. But I think there is nothing wrong with using string value enums; in fact, they are even better than the biggest enum alternative: POJO as const.
// implicit
enum LogLevel {
DEBUG,
WARNING,
ERROR,
}
// explicit with numerical values
enum LogLevel {
DEBUG = 100,
WARNING = 200,
ERROR = 300,
}
function log(msg: string, logLevel: LogLevel) {}
Those enums have a big problem. We could use log('Hey', 6000)
and TypeScript wouldn't complain.
function log(msg: string, logLevel: LogLevel) {}
log("Hey", 6000) // TypeScript doesn't complain, but it's not a valid LogLevel value
In this example, TypeScript won't raise an error even though the value 6000 is not a valid LogLevel
. Numerical enums allow any numeric value to be used, which can lead to potential issues if incorrect values are accidentally or maliciously used in the code.
String enums don't have such problems:
enum LogLevel {
DEBUG = "DEBUG",
WARNING = "WARNING",
ERROR = "ERROR",
}
log("Hey", LogLevel.DEBUG) // ✅
log("Hey", "DEBUG") // ❌ (I like that it doesn't allow using literal values directly in code)
log("Hey", "WRONG") // ❌
Object.values(LogLevel)
and Object.keys(LogLevel)
work fine for string value enums.var LogLevel
;(function (LogLevel) {
LogLevel["DEBUG"] = "Debug"
LogLevel["WARNING"] = "Warning"
LogLevel["ERROR"] = "Error"
})(LogLevel || (LogLevel = {}))
I agree it doesn't look great but I don't care. I write a code in TypeScript. To remove runtime value we can use const enum
, but why would you want to do that?
log('Hey', "DEBUG")
in the code. I want to have log('Hey', LOG_LEVEL.DEBUG)
for better refactoring and discoverability.enum LogLevel {
DEBUG = "DEBUG",
WARNING = "WARNING",
ERROR = "ERROR",
}
function log(message: string, level: LogLevel) {}
log("Hey", "DEBUG") // ❌ This fails in TypeScript.
// This is good. I don't want to use 'DEBUG' in code.
// I always want to use LogLevel.DEBUG
log("Hey", LogLevel.DEBUG) // ✅
as consts
. This argument is invalid.LogLevel.DEBUG
is the type and value in the TypeScript world. But I don't think that's a problem.enum LogLevel {
DEBUG = "DEBUG",
WARNING = "WARNING",
ERROR = "ERROR",
}
enum MessageType {
DEBUG = "DEBUG",
WARNING = "WARNING",
ERROR = "ERROR",
}
function log(message: string, level: LogLevel) {}
log("Hey", "DEBUG") // ❌ They don't like that it fails.
log("Hey", LogLevel.DEBUG) // ✅
log("Hey", MessageType.ERROR) // ❌ They don't like that it fails.
log('Hey', 'DEBUG')
. But are you writing a library?In summary, haters of enums argue that:
In those videos it's often said that POJOs as enums are a good alternative to ts enums. It's funny because they present it like it's a silver bullet which obviously it isn't.
This is a general idea:
const LogLevel = {
DEBUG: "DEBUG",
WARNING: "WARNING",
ERROR: "ERROR",
} as const
type ObjectValues<T> = T[keyof T]
type LogLevel = ObjectValues<typeof LogLevel>
function log(message: string, level: LogLevel) {
console.log(`${message}: ${level}`)
}
log("Hey", "DEBUG") // ✅ It's possible now. But is it good?
log("Hey", LogLevel.DEBUG) // ✅
A major drawback for me is the need to define this type LogLevel = ObjectValues<typeof LogLevel>;
. With enums, it works out of the box. The second problem is the possibility of using a value that is not expressed by the enum itself, e.g. log('Hey', 'DEBUG')
. That's a problem for me, not a good point.
Some similar views on the topic:
Sources:
[1]: Enums considered harmful
[2]: How to use TypeScript Enums and why not to, maybe
[3]: Let's Talk About TypeScript's Worst Feature
[4]: TypeScript Enums are Bad // Alternatives to use