Is there a (better) shorthand for TypeScript's string enums or object literals?

Viewed 979

TypeScript enums look like this:

enum Direction {
  Up = "Up",
  Down = "Down",
  Left = "Left",
  Right = "Right"
}

Enum declarations like these seem to me like unnecessary work. Especially with the DRY principle in mind. Isn't there a shorthand for declarations like this so that I don't need to repeat the values when they are equal to the keys? Sure, I could use numeric enums but they have that disadvantage that during debugging no meaningful values are present.

For example like the object literal shorthand from ES6:

firstname = 'firstname';
lastname = 'lastname';

fullname = {
  firstname,
  lastname
}

But even with this shorthand definition I still need to write for example firstname three times. Isn't there an easier way?

Would be nice if something like this would work:

// for enums:
string_enum Direction {
  Up,
  Down,
  Left,
  Right
}

// for object literals:
fullname: {
  'firstname',
  'lastname'
}
3 Answers

This is not exactly what you were asking for; it doesn't create a true enum but it does remove the duplication and it gives you enum-like behavior. We wrote this before real string enums were available and have mostly stopped using this since the duplication in string enums may not be pretty but it seldom causes bugs.

/**
 * This creates a string enum.  Use like so:
 *     const Ab = strEnum(['a', 'b']);
 *     type AbKeys = keyof typeof Ab;
 * @param keys keys in the enum
 * @returns enum object
 */
export function createStringEnum<T extends string>(keys: T[]): {[K in T]: K} {
    return keys.reduce((res, key) => {
        res[key] = key;
        return res;
    }, Object.create(null));
}

In the above case, AbKeys is the type you can use when taking in one of those object. Note that this is not a true enum because you can pass the strings in and the compiler will be happy, which would not be the case with enums

Example

const Ab = createStringEnum(['a', 'b']);
type AbKeys = keyof typeof Ab;
function choose(key: AbKeys) {
  if (key === Ab.a) {
    return 'A'
  }
  return 'B';
}
// Type error
console.log(Ab.c);
// OK
console.log(choose(Ab.b));
// A true string enum wouldn't allow this
console.log(choose('a'));
// At least, it does't allow this...
function test (a: string)  {
  console.log(choose(a));
}

See example on the TypeScript playground

Since TypeScript 3.4, you can use const assertions.

While not an enum, I'm fond of using an array like so:

const DIRECTIONS = ["Up", "Down", "Left", "Right"] as const
type Direction = typeof DIRECTIONS[number]
function goTo(direction: Direction) {
  // ...
}

goTo(" // with that open quote, you would see a list of the string literals

You don't get the benefit of having an object as a "representative" to access the strings (e.g., Direction.Up), but with any modern IDE config for TypeScript, you will get robust auto completion and type coverage.

enum Directions {
    "LEFT",
    "RIGHT",
    "UP",
    "DOWN",
}

const goTo = (direction: Directions) => {
    // use Directions[direction] to get the value.
    // Otherwise you get only the index.
    console.log("my direction is ", Directions[direction]);

    // check directions
    if (Directions[direction] === Directions[Directions.LEFT]) {
        console.log("I go left...");
    }

   // when check directions not madary to extract that value.
   if (direction === Directions.LEFT) {
      console.log("I go left...");
   }


};

goTo(Directions.LEFT);
Related