TypeScriptで固定値のセットを表現する際、enum
は自然な選択肢に見えるかもしれません。しかし、コードの保守性とパフォーマンスに影響を与える重要な制限があります。Map
がより良い選択肢である理由を見ていきましょう。
Enumの問題点
複雑なアプリケーション開発において、Enumには以下の主要な欠点があります:
- 硬直性: 動的な変更が不可能
- パフォーマンス: 冗長なJavaScriptコードを生成
- 複雑な型付け: イントロスペクションと型の合成が困難
- バンドルサイズ: 最終的なコードサイズへの影響
例を見てみましょう:
1// TypeScriptコード
2enum Status {
3 Active = 'ACTIVE',
4 Inactive = 'INACTIVE',
5}
6
7// トランスパイルされたJavaScriptコード
8var Status = {
9 Active: 'ACTIVE',
10 Inactive: 'INACTIVE',
11 ACTIVE: 'Active',
12 INACTIVE: 'Inactive',
13};
14(function (Status) {
15 Status['Active'] = 'ACTIVE';
16 Status['Inactive'] = 'INACTIVE';
17})(Status || (Status = {}));
このトランスパイルされたコードは以下を生成します:
- ミラープロパティ(キー ↔ 値)を持つオブジェクト
- 不要なIIFE(即時実行関数式)
- 各値の二重参照
- パフォーマンスに影響を与える追加コード
Mapによる解決策
Map
とTypeScriptオブジェクトは、より洗練された柔軟なアプローチを提供します:
1// constと型による解決策
2const Status = {
3 Active: 'ACTIVE',
4 Inactive: 'INACTIVE',
5} as const;
6
7// 自動的に推論される型
8type Status = (typeof Status)[keyof typeof Status];
9
10// シンプルで効率的なトランスパイル結果
11const Status = {
12 Active: 'ACTIVE',
13 Inactive: 'INACTIVE',
14};
Mapの利点
- 型安全性
1function processStatus(status: Status) {
2 // 無効なステータスでコンパイルエラー
3 console.log(status);
4}
5
6// ランタイム検証
7const isValidStatus = (status: string): status is Status =>
8 Object.values(Status).includes(status as Status);
- 拡張性
1// 動的な追加が可能
2const ExtendedStatus = {
3 ...Status,
4 Pending: 'PENDING' as const,
5};
- 高度なパターン
1// 関連設定
2const StatusConfig = {
3 [Status.Active]: {
4 color: 'green',
5 label: '有効',
6 icon: 'check',
7 },
8 [Status.Inactive]: {
9 color: 'red',
10 label: '無効',
11 icon: 'cross',
12 },
13} as const;
14
15// ユーティリティ型
16type StatusConfig = {
17 [K in Status]: {
18 color: string;
19 label: string;
20 icon: string;
21 };
22};
23
24// 型付きヘルパー関数
25function getStatusConfig(status: Status) {
26 return StatusConfig[status];
27}
実践的なユースケース
1. APIエンドポイント
1const ApiEndpoints = {
2 Users: '/api/users',
3 Products: '/api/products',
4 Orders: '/api/orders',
5} as const;
6
7type Endpoint = (typeof ApiEndpoints)[keyof typeof ApiEndpoints];
8
9// 自動的なURL型付け
10function fetchData(endpoint: Endpoint) {
11 return fetch(endpoint);
12}
2. 状態管理
1const LoadingState = {
2 Idle: 'IDLE',
3 Loading: 'LOADING',
4 Success: 'SUCCESS',
5 Error: 'ERROR',
6} as const;
7
8type LoadingState = typeof LoadingState[keyof typeof LoadingState];
9
10function handleState(state: LoadingState) {
11 switch (state) {
12 case LoadingState.Loading:
13 return <Spinner />;
14 case LoadingState.Error:
15 return <ErrorMessage />;
16 // TypeScriptが網羅性をチェック
17 }
18}
詳細な比較表
EnumとMapの詳細な比較:
型安全性
- Enum: 基本的 (✓)
- Map: 型推論付きの高度な安全性 (✓✓)
ランタイムパフォーマンス
バンドルサイズ
- Enum: 大きい (✗)
- Map: 小さい (✓)
拡張性
- Enum: 拡張が困難 (✗)
- Map: 容易に拡張可能 (✓)
イントロスペクション
TypeScript strict互換性
- Enum: 互換性あり (✓)
- Map: より多くの機能で完全互換 (✓✓)
保守性
ベストプラクティス
- 明示的な命名
1// 推奨
2const HttpStatus = {
3 Ok: 200,
4 NotFound: 404,
5 ServerError: 500,
6} as const;
7
8// 避けるべき
9const Status = {
10 a: 200,
11 b: 404,
12 c: 500,
13} as const;
- Union型
1// 型安全なユニオンの作成
2type HttpSuccessStatus = 200 | 201 | 204;
3type HttpErrorStatus = 400 | 401 | 404 | 500;
4type HttpStatus = HttpSuccessStatus | HttpErrorStatus;
- ランタイム検証
1function isHttpSuccess(status: number): status is HttpSuccessStatus {
2 return [200, 201, 204].includes(status);
3}
段階的な移行: 既存のenumコードを移行するには:1. 最も問題のあるenumを特定
2. 同等のMapを作成 3. インポートを更新 4. 使用している関数の型付けを適応
結論
Mapはenumに比べて以下の点で優れた選択肢を提供します:
- より正確で柔軟な型付け
- より良いランタイムパフォーマンス
- 簡素化された保守
- JavaScriptエコシステムとの優れた互換性
これらの概念をさらに深く理解するには:
あなたの経験を共有して、この記事に貢献してください!