What Hidden Power Does TypeScript's Enum Feature Hold For Your Code?

Enums: The Secret Ingredient to Cleaner, More Readable TypeScript Code

What Hidden Power Does TypeScript's Enum Feature Hold For Your Code?

Enums in TypeScript are an essential feature that every developer should get familiar with. They help in defining a set of named constants, making code more readable and maintainable. There are two main types of enums: numeric and string-based. Let’s dive into what enums are and how they can be effectively used.

First off, enums are a way to group related values together. This makes your code much cleaner and easier to understand. Imagine working on a project that involves different subscription tiers, and you need to define these tiers within your code. You could use an enum to achieve this.

enum AccountType {
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
  CUSTOM = "Custom",
}

Now, instead of using arbitrary strings or numbers to represent these tiers, you could use AccountType.PERSONAL or AccountType.ENTERPRISE, making your code’s intent clear.

When it comes to types of enums, TypeScript supports numeric and string enums. By default, enums in TypeScript are numeric. This means the first member is assigned the value 0 by default, and the subsequent members are assigned incremental values unless specified otherwise.

enum BillingSchedule {
  FREE = 0,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}

In the example above, BillingSchedule.FREE is 0, BillingSchedule.MONTHLY is 1, and the increments continue. You can manually assign values too.

String enums, on the other hand, use string values. Every member must be initialized with a string literal or another string enum member.

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

String enums are quite effective when you need descriptive strings instead of numbers.

Enum members can be initialized in various ways. They can either be constant or computed. Constant members are those evaluated at compile time.

enum FileAccess {
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
}

In this case, None is 0, Read is 2, Write is 4, and ReadWrite is 6.

Computed members can’t be evaluated at compile time. Let’s use an example to make it clear:

enum Weekdays {
  Monday = 1,
  Tuesday = Monday + 1,
  Wednesday = Tuesday + 1,
  Thursday = Wednesday + 1,
  Friday = Thursday + 1,
  Saturday = Friday + 1,
  Sunday = Saturday + 1,
}

Here, the values from Tuesday through Sunday are computed from the value of the previous member.

One of the cool things about TypeScript enums is that they are real objects at runtime. This means you can pass them around to functions and access their properties, just like with any other object.

enum E {
  X,
  Y,
  Z,
}

function f(obj: { X: number }) {
  return obj.X;
}

f(E); // Works because E has a property named 'X' which is a number.

Enums are also handy when used in classes to define and restrict values for properties or methods.

class PersonalSubscription {
  private accountType: AccountType;
  private billingSchedule: BillingSchedule;

  constructor(accountType: AccountType, billingSchedule: BillingSchedule) {
    this.accountType = accountType;
    this.billingSchedule = billingSchedule;
  }

  getAccountType(): AccountType {
    return this.accountType;
  }

  getBillingSchedule(): BillingSchedule {
    return this.billingSchedule;
  }
}

const subscription = new PersonalSubscription(AccountType.PERSONAL, BillingSchedule.MONTHLY);
console.log(subscription.getAccountType()); // "Personal"
console.log(subscription.getBillingSchedule()); // 1

Converting an enum member to a string is straightforward since TypeScript enums are real objects at runtime. Converting a string back to an enum type, however, requires a bit more effort, especially for string enums.

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

function getDirectionFromString(value: string): Direction | undefined {
  return Object.values(Direction).includes(value as Direction) ? (value as Direction) : undefined;
}

let myDirection = getDirectionFromString('UP');
console.log(myDirection); // "UP"

Advanced enum features include const enums and union enums. Const enums are completely removed during TypeScript’s compilation, allowing for optimizations by inlining members at usage sites.

const enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

let directions = [Direction.Up, Direction.Down];
console.log(directions); // ["UP", "DOWN"]

Union enums and type safety can restrict function parameters or variables to allow only certain values.

enum Fruit {
  Apple,
  Banana,
  Cherry,
}

function eatFruit(fruit: Fruit) {
  console.log('Eating ' + Fruit[fruit]);
}

eatFruit(Fruit.Apple); // Correct
eatFruit(5); // Error: Argument of type '5' is not assignable to parameter of type 'Fruit'.

Enums are most useful when representing a fixed set of values known at compile time. They shine in scenarios where you need to define a set of distinct cases, like days of the week.

enum Days {
  Sunday = 1,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
}

In summary, TypeScript enums provide a clear and expressive way to define and work with named constants. Whether using numeric or string enums, they improve code readability and maintainability. By taking advantage of the various features of enums, you can write more robust and type-safe code.

So next time you’re diving into a TypeScript project, don’t overlook the power of enums. They might just be the tool you need to bring clarity and structure to your code.