TypeScript PR

【TypeScript】enumとは?基本的な使い方と非推奨とされている5つの理由

【TypeScript】enumとは?基本的な使い方と非推奨とされている5つの理由
記事内に商品プロモーションを含む場合があります

こんにちは!

TypeScriptを使ったことがある開発者なら、一度はenumという機能に出会ったことがあるでしょう。一見便利に見えるこの機能ですが、実は様々な落とし穴が潜んでいます。

enumって便利そうだけど、最近は使わない方がいいって聞いたけど本当?
TypeScriptにenumがあるのは知ってるけど、どんな問題があるんだろう?
enumの代わりに何を使えばいいの?

こんな疑問をお持ちの方も多いのではないでしょうか。

この記事では、TypeScriptのenumの基本から、なぜ現代のTypeScriptでは避けられる傾向にあるのか、そして代わりに何を使うべきなのかについて詳しく解説します。

この記事は、以下のような方におすすめです。

この記事はこんな人におすすめ!
  • TypeScriptのenumの基本を理解したい方
  • enumが非推奨とされる理由を知りたい方
  • enumの代わりに使える最新の代替手段を学びたい方
  • より型安全なコードを書きたい方

この記事を読めば、TypeScriptのenumについての理解が深まるだけでなく、より型安全で保守性の高いコードを書くための知識を得ることができます。TypeScriptの進化と共に、コーディング手法も進化していくことの重要性も理解できるでしょう。

それでは、順を追って詳しく見ていきましょう!

TypeScript enumとは?基本概念を理解しよう

まずは、enumの基本概念から理解していきましょう。

enumの定義と基本的な使い方

enumとは「列挙型(Enumeration Type)」の略で、関連する値の集合に名前をつけて管理するための機能です。簡単に言えば、特定の値の集まりに名前をつけて、コード内で使いやすくする仕組みといえます。

基本的なenumの定義方法は以下のようになります。

enum Direction {
  Up,
  Down,
  Left,
  Right
}

この例では、Directionという名前のenumを定義し、その中にUpDownLeftRightという4つの値を持たせています。

enumを使うことで、以下のようなコードが書けるようになります。

// enumの値を使用する
let move: Direction = Direction.Up;

// 条件分岐でenumを使用する
if (move === Direction.Up) {
  console.log("上に移動します");
}

enumの特徴と利点

enumを使うことで得られる主な利点は以下の通りです。

  • コードの可読性向上:数値の代わりに意味のある名前を使えるため、コードが読みやすくなります。
  • 型安全性の確保:enumで定義された値以外は使用できないため、誤った値が混入するのを防げます。
  • 自動補完のサポート:IDEの自動補完機能によって、使用可能な値を簡単に選択できます。
  • リファクタリングの容易さ:値の変更が必要になった場合も、一箇所の修正で済みます。

例えば、ゲーム開発でキャラクターの状態を管理する場合、以下のようにenumを使うことができます。

enum PlayerState {
  Idle,
  Running,
  Jumping,
  Falling
}

// プレイヤーの状態を管理
let currentState: PlayerState = PlayerState.Idle;

// 状態に応じた処理
function updatePlayer(state: PlayerState) {
  switch (state) {
    case PlayerState.Idle:
      console.log("待機中のアニメーションを再生");
      break;
    case PlayerState.Running:
      console.log("走るアニメーションを再生");
      break;
    case PlayerState.Jumping:
      console.log("ジャンプのアニメーションを再生");
      break;
    case PlayerState.Falling:
      console.log("落下のアニメーションを再生");
      break;
  }
}

このように、enumを使うことで、コード内で特定の状態や値を扱いやすくなります。

TypeScript enumの種類と詳細な使い方

TypeScriptのenumには、数値enum、文字列enum、ヘテロジニアスenumの3種類があります。それぞれの特徴と使い方を詳しく見ていきましょう。

数値enum(Numeric Enum)

数値enumは、TypeScriptで最もよく使われるenum形式で、各メンバーに数値が自動的に割り当てられます。デフォルトでは0から始まり、順番に1ずつ増加していきます。

enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

特定の値から始めたい場合は、明示的に値を指定することもできます。

enum Direction {
  Up = 1,    // 1
  Down,      // 2
  Left,      // 3
  Right      // 4
}

また、すべての値を個別に指定することも可能です。

enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  ServerError = 500
}

数値enumの特徴は、双方向マッピングがあることです。つまり、名前から値を取得することも、値から名前を取得することも可能です。

// 名前から値を取得
console.log(HttpStatus.OK); // 200

// 値から名前を取得
console.log(HttpStatus[200]); // "OK"

文字列enum(String Enum)

文字列enumは、各メンバーに文字列値を割り当てるenum形式です。数値enumと異なり、すべてのメンバーに明示的に値を指定する必要があります

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

文字列enumの主な利点は、デバッグがしやすいことです。ログやエラーメッセージに表示される値が数値ではなく文字列なので、何を表しているのかが一目で分かります。

// 文字列enumの使用例
const moveDirection: Direction = Direction.Up;
console.log(moveDirection); // "UP"

ただし、文字列enumには双方向マッピングがないため、値から名前を取得することはできません。

ヘテロジニアスenum(Heterogeneous Enum)

ヘテロジニアスenumは、数値と文字列を混在させたenum形式です。実務ではあまり使われませんが、特殊なケースで役立つことがあります。

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES"
}

ヘテロジニアスenumは柔軟性がありますが、型の一貫性が失われるため、できるだけ同じ型(数値または文字列)で統一することが推奨されています。

計算されたメンバーと定数メンバー

enumのメンバーは、定数値または計算された値を持つことができます。

定数メンバーは、コンパイル時に値が決定するメンバーです。

enum FileAccess {
  // 定数メンバー
  None = 0,
  Read = 1 << 0,  // 1 (ビット演算)
  Write = 1 << 1, // 2
  ReadWrite = Read | Write // 3 (Read OR Write)
}

計算されたメンバーは、実行時に値が決定するメンバーです。

enum Random {
  // 計算されたメンバー
  Value = Math.floor(Math.random() * 1000)
}

実際のプロジェクトでは、定数メンバーを使用することが多いですが、特定の場合には計算されたメンバーも役立ちます。

TypeScript enumの問題点と非推奨の理由

TypeScriptのenumは便利な機能ですが、いくつかの問題点があり、最近のTypeScriptコミュニティでは必ずしも推奨されていない傾向があります。その主な理由を見ていきましょう。

コンパイル後のコードサイズ

数値enumは、JavaScriptにコンパイルされると、かなり大きなコードになります。以下のTypeScriptコードを見てみましょう。

enum Direction {
  Up,
  Down,
  Left,
  Right
}

このシンプルなenumが、以下のようなJavaScriptコードにコンパイルされます。

"use strict";
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

この変換により、バンドルサイズが増加し、特に多くのenumを使用するプロジェクトではパフォーマンスに影響を与える可能性があります。

ツリーシェイキングの問題

ツリーシェイキング(使用されていないコードを削除する最適化技術)との相性が悪いという問題もあります。通常のオブジェクトや型と異なり、enumは完全にツリーシェイキングで除外されない場合があります。

数値enumの型安全性の問題

数値enumには型安全性に関する重大な問題があります。数値enumに任意の数値を渡せてしまう問題があり、型安全とは言えません。以下のような例では、enumの範囲外の数値でも受け入れられてしまいます:

enum PkmnTypes {
  fire,   // 0
  grass,  // 1
  water   // 2
}

function assignPkmnType(pkmntype: PkmnTypes): void {}

assignPkmnType(PkmnTypes.fire); // OK
assignPkmnType(1);              // OK (grass)
assignPkmnType(999);            // これもエラーにならない!(TypeScript 5.0未満)

TypeScript 5.0では改善されましたが、それ以前のバージョンではこの問題が存在します。

存在しない値へのアクセスの問題

数値enumで存在しない値を指定した場合でも、コンパイルエラーにならないという問題もあります。これは型安全性を損なう大きな要因です。

enum Status {
  Active = 1,
  Inactive = 2,
}

console.log(Status[1]); // Active
console.log(Status[3]); // undefined - コンパイルエラーにならない!

このように、存在しない値(3)にアクセスしても、TypeScriptはエラーを出さず、単にundefinedを返します。これにより、バグの発見が遅れる可能性があります。

双方向マッピングの複雑さ

数値enumと文字列enumでは振る舞いが異なります。数値enumは値からキー、キーから値の双方向マッピングを生成しますが、文字列enumでは生成されません。 この一貫性のなさが混乱を招く可能性があります。

例えば、数値enumの場合:

enum NumericEnum {
  A,
  B,
  C
}

// NumericEnumのキー数は6になる(キーと値の両方がマッピングされるため)
console.log(Object.keys(NumericEnum).length); // 6

一方、文字列enumの場合:

enum StringEnum {
  A = "A",
  B = "B",
  C = "C"
}

// StringEnumのキー数は3になる(文字列enumでは双方向マッピングがないため)
console.log(Object.keys(StringEnum).length); // 3

TypeScript enumの代替手段

enumの問題点を踏まえて、最新のTypeScriptでは以下の代替手段が推奨されています。それぞれの特徴と使い方を見ていきましょう。

Union Types(ユニオン型)

もっともシンプルな代替手段はユニオン型です。文字列リテラルのユニオンを使用することで、enumと同様の型安全性を確保できます。

// enumの代わりにユニオン型を使用
type Direction = "Up" | "Down" | "Left" | "Right";

// 使用例
function move(direction: Direction) {
  switch (direction) {
    case "Up":
      return { x: 0, y: 1 };
    case "Down":
      return { x: 0, y: -1 };
    case "Left":
      return { x: -1, y: 0 };
    case "Right":
      return { x: 1, y: 0 };
  }
}

// 関数呼び出し
const position = move("Up");

ユニオン型のメリットは、シンプルで分かりやすく、ツリーシェイキングとも相性が良いことです。ただし、値と名前が同じであるため、数値を扱いたい場合には適していません。

具体例を見てみましょう。

// 文字列リテラルのユニオン型
type Status = "active" | "inactive" | "pending";

// 数値を扱いたい場合の問題
// 例えば、HTTPステータスコードを表現しようとすると...
type HttpStatus = 200 | 201 | 400 | 404 | 500;

// このように使う
function handleStatus(status: HttpStatus) {
  // 数値の範囲チェックなどが難しい
  if (status >= 400) { // エラー: '>=' 演算子をこのコンテキストで使用することはできません
    console.log("エラーが発生しました");
  }
}

// 名前と値の関連付けがないため、コード内での参照が数値リテラルになる
function fetchData() {
  return {
    status: 200 // コードを読む人は200が何を意味するか理解しにくい
  };
}

// 対照的にenumだと名前で参照できる
enum HttpStatusEnum {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  NotFound = 404,
  ServerError = 500
}

function betterFetchData() {
  return {
    status: HttpStatusEnum.OK // 意味が明確
  };
}

このように、数値を扱う場合や名前と値を明確に関連付けたい場合は、単純なユニオン型では不十分なことがあります。次に説明するas constを使用したオブジェクトが、このような場合により適しています。

const assertionsを使用したオブジェクト

as const(const assertions)を使用したオブジェクトは、enumの強力な代替手段です。これにより、オブジェクトのプロパティを読み取り専用にし、その値の型を具体的なリテラル型として扱うことができます。

// as constを使用したオブジェクト
const Direction = {
  Up: "up",
  Down: "down",
  Left: "left",
  Right: "right"
} as const;

// 値の型を取得
type Direction = typeof Direction[keyof typeof Direction];
// "up" | "down" | "left" | "right" 型になる

// 使用例
function move(direction: Direction) {
  switch (direction) {
    case Direction.Up:
      return { x: 0, y: 1 };
    case Direction.Down:
      return { x: 0, y: -1 };
    case Direction.Left:
      return { x: -1, y: 0 };
    case Direction.Right:
      return { x: 1, y: 0 };
  }
}

const position = move(Direction.Up);

この方法のメリットは、enumと同様の使い勝手を保ちつつ、ツリーシェイキングとの相性が良いことです。また、コンパイル後のコードもシンプルになります。

数値を扱う場合のconst assertions

数値を扱いたい場合も、as constを使用することができます。

const HttpStatus = {
  OK: 200,
  Created: 201,
  BadRequest: 400,
  Unauthorized: 401,
  NotFound: 404,
  ServerError: 500
} as const;

type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus];
// 200 | 201 | 400 | 401 | 404 | 500 型になる

// 使用例
function handleResponse(status: HttpStatus) {
  if (status === HttpStatus.OK) {
    console.log("リクエスト成功");
  } else if (status >= HttpStatus.BadRequest) {
    console.log("エラーが発生しました");
  }
}

このアプローチは、数値enumの代替として特に有効です。

enum代替手段の比較

それぞれの方法の特徴を比較してみましょう。

特徴 enum ユニオン型 as const オブジェクト
型安全性 高い 高い 高い
コンパイル後のコードサイズ 大きい 小さい 小さい
ツリーシェイキング対応 不十分 優れている 優れている
IDE自動補完 優れている 限定的 優れている
数値と文字列の区別 可能 困難 可能
実行時の利用 可能 困難 可能

この比較から、多くの場合、as constを使用したオブジェクトがenumの最良の代替手段といえるでしょう。ただし、シンプルな用途ではユニオン型も有効な選択肢です。

【付録】さらに学びを深めるためのリソース


intersection型について理解を深めたところで、さらに学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。

おすすめの書籍


プロを目指す人のためのTypeScript入門


プロ開発者への近道!基礎から応用まで幅広くカバー

1一番のおすすめは「プロを目指す人のためのTypeScript入門」です。
この本は、TypeScriptの基礎から高度な使い方まで幅広く解説しています。

初心者でも理解しやすい説明から始まり、徐々に難易度を上げていく構成が特徴。
プログラミング経験者なら、さらに深い理解が得られるでしょう。

実際の開発現場で使えるテクニックも豊富に紹介されているのがポイント。
型の使い方や設計パターンなど、実践的な内容が満載です。

プロの開発者を目指す人はもちろん、TypeScriptをしっかり学びたい人におすすめの一冊。
基礎固めから応用力の向上まで、幅広いニーズに応えてくれます。

現場で使えるTypeScript 詳解実践ガイド


実践的スキルを身につけたい人必見!現場のノウハウが詰まった一冊

次におすすめは「現場で使えるTypeScript 詳解実践ガイド」がランクイン。
この本の特徴は、実際の開発現場で役立つ知識が詰まっていること。

TypeScriptの基本的な文法から始まり、実際のプロジェクトでどう活用するかまで丁寧に解説しています。
エラー対処法や性能改善のコツなど、現場ならではの知恵も満載。

特に、大規模なプロジェクトでTypeScriptを使う際のベストプラクティスが学べるのが魅力。
コードの保守性や再利用性を高める方法も詳しく紹介されています。

すでにJavaScriptの経験がある人や、実務でTypeScriptを使いたい人におすすめ。
この本を読めば、現場で即戦力として活躍できる力が身につくはずです。

書籍に関してはこちらの記事も参考にしてくださいね!




オンラインで参照できる公式ドキュメント


TypeScript公式ハンドブック


https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。

TypeScript Deep Dive


https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。

TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。

キャリア形成/給与還元
ひとつひとつ真摯に向き合う企業
ONE_WEDGE社員募集

株式会社 ONE WEDGEでは、新たな仲間を募集しています!

私たちと一緒に、革新的で充実したキャリアを築きませんか?
当社は、従業員が仕事と私生活のバランスを大切にできるよう、充実した福利厚生を整えています。

  • 完全週休2日制(土日休み)で、祝日や夏季休暇、年末年始休暇もしっかり保証!
  • 様々な休暇制度(有給、慶弔、産前・産後、育児、バースデー休暇、有給6日取得で特別休暇付与)を完備!
  • 従業員の成長と健康を支援するための表彰制度、資格取得支援、健康促進手当など!
  • 生活を支えるテレワーク手当、記事寄稿手当、結婚祝金・出産祝金など、様々な手当を提供!
  • 自己啓発としての書籍購入制度や、メンバー間のコミュニケーションを深める交流費補助!
  • 成果に応じた決算賞与や、リファラル採用手当、AI手当など、頑張りをしっかり評価!
  • ワークライフバランスを重視し、副業もOK!

株式会社 ONE WEDGEでは、一人ひとりの従業員が自己実現できる環境を大切にしています。
共に成長し、刺激を与え合える仲間をお待ちしております。
あなたの能力と熱意を、ぜひ当社で発揮してください。
ご応募お待ちしております!

ホームページ、採用情報は下記ボタンからご確認ください!

応募、ご質問など、LINEでお気軽にご相談ください♪

まとめ

この記事では、TypeScriptのenumについて詳しく解説してきました。改めて重要なポイントをおさらいしましょう。

  • TypeScriptのenumは関連する値の集合に名前をつけて管理するための機能です。
  • 数値enum、文字列enum、ヘテロジニアスenumの3種類があることを学びました。
  • enumを使うことで、コードの可読性や型安全性が向上することがわかりました。
  • しかし、コンパイル後のコードサイズやツリーシェイキングとの相性など、多くの問題点があることも理解しました。
  • 存在しない値へのアクセスやenumの互換性など、型安全性に関わる深刻な問題も存在することがわかりました。
  • 最新のTypeScriptでは、ユニオン型やas constを使用した代替手段が推奨されていることを学びました。

TypeScriptのenumは魅力的な機能に見えますが、現代の開発においては避けるべき問題を多く抱えています。代替手段を理解した上で、より型安全で保守性の高いコードを書くことを心がけましょう。この記事が、あなたのTypeScriptコーディングの参考になれば幸いです。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です