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