DEV Community

Cover image for An alternative solution to Typescript enums
Paul Guilbert
Paul Guilbert

Posted on • Updated on

An alternative solution to Typescript enums

Enums are quite handy to define a set of named constants. In the TypeScript world, enums hold a unique position as both a type and a value.

However, enum comes with drawbacks...

  • Enums and const enums, are an exception to the Typescript structural typing. Meaning that Level1.INFO !== Level2.INFO !== 'INFO' even when both Level1.Info and Level2.INFO are set to 'INFO'.

  • Enums are not necessarily enumerable at runtime, and Object.values(MyEnums) can have unexpected results.

  • Enums and const enums, are more a legacy feature than a recommended one.

Let's take an example:

enum Level {
  VERBOSE = 'VERBOSE',
  NORMAL = 'NORMAL',
  DEBUG = 'DEBUG'  
}

function log(level: Level) {}

log('DEBUG') // πŸ’₯ Argument of type '"DEBUG"' is not assignable to parameter of type 'Level'
log(Level.DEBUG) // βœ…

const allLevels = Object.values(Level) 
// allLevels = ["NONE", 0, "VERBOSE", "NORMAL", "DEBUG"] πŸ€”
Enter fullscreen mode Exit fullscreen mode

While it operates as intended, the behavior of enums may seem somewhat strange to a TypeScript user.

The alternative solution

Fortunately, TypeScript offers features that, when combined, provide similar functionality to enums:

  • const object
  • union type
  • value and a type can coexist with the same name

This leads to the following code that "fixed" the unwanted behavior of enums:

const Level = {
  NONE : 0,
  VERBOSE: 'VERBOSE',
  NORMAL: 'NORMAL',
  DEBUG: 'DEBUG'
} as const
type Level = (typeof Level)[keyof typeof Level]

function log(level: Level) {}

log('DEBUG') // βœ…
log(Level.DEBUG) // βœ…


const allLevels = Object.values(Level) 
// [0, "VERBOSE", "NORMAL", "DEBUG"] βœ…
Enter fullscreen mode Exit fullscreen mode

The clear advantage of this approach is that, in most cases, it's possible to seamlessly replace enums without modifying existing code that uses them.

Additionally, the union type can be simplified using the utility type:

type UnionOfPropertyTypes<T extends object> = T[keyof T]
Enter fullscreen mode Exit fullscreen mode

Resulting in a cleaner declaration:

const Level = {
  NONE : 0,
  VERBOSE: 'VERBOSE',
  NORMAL: 'NORMAL',
  DEBUG: 'DEBUG'
} as const
type Level = UnionOfPropertyTypes<typeof Level>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)