Cover image for TypeScriptのString Enumsを少し便利にするTips
typescript

TypeScriptのString Enumsを少し便利にするTips

April 04, 2019

3 min read

mitsuruogMitsuru Ogawa

TypeScript の小ネタです。

TypeScript では次のように string を Enum として定義することができるのですが、string を Enum に代入するときにエラーとなってしまうため、今まで少し使いにくいと思っていました。

今日はその認識を改めます。

enum Direction {
  North = "North",
  South = "South",
  East = "East",
  West = "West",
}

let direction: Direction;

direction = Direction.North;
direction = "North"; // Error Type '"North"' is not assignable to type 'Direction'.
direction = "AnythingElse"; // Error

TypeScript Deep Diveを読んでいたら、String Enums で Enum と string の両方に対応できる方法が載ってきたので紹介します。

まず string[]から Enum を生成するユーティリティ関数を作成します。

function stringToEnum<T extends string>(o: T[]): { [K in T]: K } {
  return o.reduce((accumulator, currentValue) => {
    accumulator[currentValue] = currentValue;
    return accumulator;
  }, Object.create(null));
}

この関数は、Tタイプの配列を受け取って、Tの中にある要素(K)をキーと値にセットした Object を返します。

reduce の初期値にObject.create(null)を利用しているのは、プレーンな Object をベースに Enum を生成するためです。こちらの記事がよくまとまっています。

ちなみに{}を初期値として使った場合は、次のようなエラーが発生します。TypeScript 賢い!

Type '{}' is not assignable to type '{ [K in T]: K; }

次に生成した Enum(オブジェクト)から Type を生成します。

const Direction = stringToEnum(["North", "South", "East", "West"]);

// keyofでDirectionのキーを抽出して
// typeofでUnion Typeを生成する
type Direction = keyof typeof Direction;

これを使うことで Enum でも string でも代入することができます。

let direction: Direction;

direction = Direction.North;
direction = "North"; // Works!!
direction = "AnythingElse"; // Error

正確には Enum ではなく、string の Map オブジェクトを Enum っぽく使っている感じですね。