🐈

TypeScriptのEnumを卒業して型安全性を高める - Union Typeによるリファクタリング

に公開

はじめに

TypeScript で開発を進める際、定数値の管理にEnumを使用している方もいらっしゃるのではないでしょうか。
しかし、実際のプロダクト開発では、Enum が原因で型安全性が損なわれたり、予期しないバグが発生することがあります。

本記事では、実際に遭遇した Enum の課題と、Union Type を活用した解決方法について紹介します。

Enum 使用時の課題

Enum を使用していた当初、以下のような問題に遭遇しました。

  1. 実行時の型安全性の欠如

  2. 予期しない値の混入

また、遭遇はしませんでしたが、Enum のリバースマッピングによる混乱も考慮する必要があります。

  1. リバースマッピングによる混乱

Enum 実装の問題点

まず、問題のある Enum 実装を見てみましょう。

  1. 型安全性の欠如
// 型チェックが効かない
const activityType: ActivityType = 999 as ActivityType;
console.log(activityType); // 999 型エラーにならない
  1. 予期しない値の混入
// APIから取得した値をそのままEnumに代入
const apiResponse = { activityType: 15 }; // 存在しない値
const activity: ActivityType = apiResponse.activityType as ActivityType;
// 実行時まで問題に気づけない
  1. リバースマッピングによる混乱
// Enumは数値から文字列への逆引きが可能(意図しない動作)
console.log(ActivityType[1]); // "LOGIN"
console.log(ActivityType["LOGIN"]); // 1

// 予期しない値でも通ってしまう
const unknownValue = ActivityType[999]; // undefined だが型エラーにならない

Union Type を活用した解決

これらの問題を解決するため、const assertion と Union Type を組み合わせた実装に変更しました。

export const ACTIVITY_TYPES = {
  // ログイン
  LOGIN: 1,
  JOIN: 2,
  LEAVE: 3,
} as const;

// アクティビティタイプの値(数値リテラル型)
export type ActivityType = (typeof ACTIVITY_TYPES)[keyof typeof ACTIVITY_TYPES];

// 型ガード関数
export const isActivityType = (value: number): value is ActivityType => {
  return Object.values(ACTIVITY_TYPES).includes(value as ActivityType);
};

// アクティビティタイプから名前を取得する関数
export const getActivityTypeName = (type: ActivityType): string => {
  const entry = Object.entries(ACTIVITY_TYPES).find(
    ([, value]) => value === type
  );
  return entry ? entry[0] : "Unknown Activity Type";
};

改善された使用例

// 型安全な実装
function logActivity(activityType: ActivityType) {
  const activityName = getActivityTypeName(activityType);
  console.log(`Activity: ${activityName} (${activityType})`);
}

// APIレスポンスの安全な処理
function processActivityFromApi(response: {
  activityType: number;
}): ActivityType | null {
  if (isActivityType(response.activityType)) {
    return response.activityType;
  }
  console.error(`Invalid activity type received: ${response.activityType}`);
  return null; // エラーハンドリング
}

まとめ

TypeScript の Enum から Union Type へ移行したことにより、不正な値の混入を防止し型安全性を向上させることができました。

Enum を特に理由なく多用している場合は、段階的に Union Type への移行を検討することで、より型安全に開発を進めることができると思います。

この記事で紹介した方法以外にも様々な手法があるようなので、他にも良い方法があればコメントで教えていただけると嬉しいです。

参考リンク

https://typescriptbook.jp/reference/values-types-variables/enum/enum-problems-and-alternatives-to-enums

GitHubで編集を提案

Discussion