TypeScriptのString Enumsを少し便利にするTips
2019-04-05

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

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っぽく使っている感じですね。