メインコンテンツへスキップ

enum の使用

Flow enum は、union 型の構文ではありません。それらは独自の型であり、Flow enum の各メンバーは同じ型を持ちます。大きな union 型は、Flow が各メンバーを別々の型として考慮する必要があるため、パフォーマンスの問題を引き起こす可能性があります。Flow enum では、enum がどれほど大きくても、Flow は追跡する型が 1 つしかないため、常に優れたパフォーマンスを発揮します。

以下の例では、次の enum を使用します。

enum Status {
Active,
Paused,
Off,
}

enum メンバーへのアクセス

ドット構文でメンバーにアクセスします

const status = Status.Active;

算出されたアクセスは使用できません

1enum Status {2  Active,3  Paused,4  Off,5}6const x = "Active";7Status[x]; // Error: computed access on enums is not allowed
7:8-7:8: Cannot access `x` on enum `Status` [1] because computed access is not allowed on enums. [invalid-enum-access]

型注釈としての使用

enum の宣言は、値(enum メンバーとメソッドにアクセスできる)と、同じ名前の型(enum メンバーの型)の両方を定義します。

function calculateStatus(): Status {
...
}

const status: Status = calculateStatus();

表現型へのキャスト

enum は、その表現型や逆と暗黙的に強制されません。enum 型から表現型に変換する場合は、明示的なキャスト `(x: string)` を使用できます。

1enum Status {2  Active,3  Paused,4  Off,5}6
7const s: string = Status.Active; // Error: 'Status' is not compatible with 'string'
8const statusString: string = Status.Active as string;
7:19-7:31: Cannot assign `Status.Active` to `s` because `Status` [1] is incompatible with string [2]. You can explicitly cast your enum value to a string using `<expr> as string`. [incompatible-type]

null 許容の enum 型を null 許容の文字列に変換するには、次のようにします。

const maybeStatus: ?Status = ....;
const maybeStatusString: ?string = maybeStatus && (maybeStatus as string);

表現型(例:`string`)から enum 型に変換する場合(有効な場合)、cast メソッドを確認してください。

メソッド

enum の宣言には、便利なメソッドも定義されています。

以下では、`TEnum` は enum の型(例:`Status`)であり、`TRepresentationType` はその enum の表現型の型(例:`string`)です。

.cast

型: cast(input: ?TRepresentationType): TEnum | void

cast メソッドを使用すると、`string` などのプリミティブ値を enum 型に安全に変換できます(enum の有効な値の場合)。それ以外の場合は、`undefined` に変換されます。

const data: string = getData();
const maybeStatus: Status | void = Status.cast(data);
if (maybeStatus != null) {
const status: Status = maybeStatus;
// do stuff with status
}

?? 演算子を使用して、1 行でデフォルト値を設定します

const status: Status = Status.cast(data) ?? Status.Off;

cast の引数の型は、enum の型によって異なります。文字列 enum の場合、引数の型は `string` になります。数値 enum の場合、引数の型は `number` になります。など。mixed 値をキャストする場合は、最初に `typeof` を使用して絞り込みます

const data: mixed = ...;
if (typeof data === 'string') {
const maybeStatus: Status | void = Status.cast(data);
}

cast は(enum メンバーのオブジェクトを表す)this を使用するため、関数自体を値として渡す場合は、アロー関数を使用する必要があります。例えば

const strings: Array<string> = ...;
// WRONG: const statuses: Array<?Status> = strings.map(Status.cast);
const statuses: Array<?Status> = strings.map((input) => Status.cast(input)); // Correct

ランタイムコスト:ミラー化された文字列 enum(例:`enum E {A, B}`)の場合、メンバー名は値と同じであるため、ランタイムコストは定数です。これは、`hasOwnProperty` を呼び出すのと同等です。他の enum の場合、最初の呼び出しで `Map` が作成され、後続の呼び出しではキャッシュされたマップで `has` が呼び出されるだけです。したがって、コストは均一な定数です。

.isValid

型: isValid(input: ?TRepresentationType): boolean

isValid メソッドは `cast` のようなものですが、単にブール値を返します。指定された入力が有効な enum 値の場合は `true`、そうでない場合は `false` です。

const data: string = getData();
const isStatus: boolean = Status.isValid(data);

isValid は(enum メンバーのオブジェクトを表す)this を使用するため、関数自体を値として渡す場合は、アロー関数を使用する必要があります。例えば

const strings: Array<string> = ...;
// WRONG: const statusStrings = strings.filter(Status.isValid);
const statusStrings = strings.filter((input) => Status.isValid(input)); // Correct

ランタイムコスト:上記の .cast の説明と同じです。

.members

型: members(): Iterator<TEnum>

members メソッドは、すべての enum メンバーのイテレーター(反復可能)を返します。

const buttons = [];
function getButtonForStatus(status: Status) { ... }

for (const status of Status.members()) {
buttons.push(getButtonForStatus(status));
}

反復順序は、宣言内のメンバーの順序と同じになることが保証されています。

enum 自体は列挙可能または反復可能ではありません(例:enum に対する for-in/for-of ループは、そのメンバーを反復処理しません)。そのためには、.members() メソッドを使用する必要があります。

Array.from(Status.members()) を使用して、反復可能オブジェクトを `Array` に変換できます。Array.from の 2 番目の引数を利用して、配列を構築すると同時に値をマッピングできます。例:

const buttonArray = Array.from(
Status.members(),
status => getButtonForStatus(status),
);

.getName

型: getName(value: TEnum): string

getName メソッドは、enum 値をその値の enum メンバーの文字列名にマッピングします。number/`boolean` /`symbol` enum を使用する場合、これはデバッグや内部 CRUD UI の生成に役立ちます。例えば

enum Status {
Active = 1,
Paused = 2,
Off = 3,
}
const status: Status = ...;

console.log(Status.getName(status));
// Will print a string, either "Active", "Paused", or "Off" depending on the value.

ランタイムコスト:上記の .cast の説明と同じです。enum 値から enum 名への単一のキャッシュされた逆マップが、`.cast`、`.isValid`、および `.getName` に使用されます。これらのメソッドの最初の呼び出しで、このキャッシュされたマップが作成されます。

switch を使用して enum を網羅的にチェックする

switch ステートメントで enum 値をチェックするときは、可能なすべての enum メンバーに対してチェックし、冗長なケースを含めないように強制します。これにより、enum を使用するコードを作成するときに、すべての可能性を考慮できるようになります。特に、メンバーを追加または削除するときに、更新する必要があるさまざまな場所を指摘することで、リファクタリングに役立ちます。

const status: Status = ...;

switch (status) { // Good, all members checked
case Status.Active:
break;
case Status.Paused:
break;
case Status.Off:
break;
}

default を使用して、これまでにチェックされていないすべてのメンバーに一致させることができます

switch (status) {
case Status.Active:
break;
default: // When `Status.Paused` or `Status.Off`
break;
}

1 つの switch case で複数の enum メンバーをチェックできます

switch (status) {
case Status.Active:
case Status.Paused:
break;
case Status.Off:
break;
}

enum のすべてのメンバーに一致する必要があります(または、default case を指定します)

1enum Status {2  Active = 1,3  Paused = 2,4  Off = 3,5}6const status: Status = Status.Active;7
8// Error: you haven't checked 'Status.Off' in the switch9switch (status) {
10 case Status.Active:11 break;12 case Status.Paused:13 break;14}
9:9-9:14: Incomplete exhaustive check: the member `Off` of enum `Status` [1] has not been considered in check of `status`. [invalid-exhaustive-check]

(これはデッドコードになるため)ケースを繰り返すことはできません。

1enum Status {2  Active = 1,3  Paused = 2,4  Off = 3,5}6const status: Status = Status.Active;7
8switch (status) {9  case Status.Active:10    break;11  case Status.Paused:12    break;13  case Status.Off:14    break;15  case Status.Paused: // Error: you already checked for 'Status.Paused'
16 break;17}
15:8-15:20: Invalid exhaustive check: case checks for enum member `Paused` of `Status` [1], but member `Paused` was already checked at case [2]. [invalid-exhaustive-check]

すべてのケースにすでに一致している場合は、default case は冗長です

1enum Status {2  Active = 1,3  Paused = 2,4  Off = 3,5}6const status: Status = Status.Active;7
8switch (status) {9  case Status.Active:10    break;11  case Status.Paused:12    break;13  case Status.Off:14    break;15  default: // Error: you've already checked all cases, the 'default' is redundant
16 break;
17}18// The following is OK because the `default` covers the `Status.Off` case:19switch (status) {20 case Status.Active:21 break;22 case Status.Paused:23 break;24 default:25 break;26}
15:3-16:10: Invalid exhaustive check: default case checks for additional enum members of `Status` [1], but all of its members have already been checked. [invalid-exhaustive-check]

不明なメンバーを持つ enum を切り替えている場合を除きます。

網羅的にチェックされた switch を網羅的にチェックされた switch の内側にネストしていて、各ブランチから戻る場合は、ネストされた switch の後に `break;` を追加する必要があります

switch (status) {
case Status.Active:
return 1;
case Status.Paused:
return 2;
case Status.Off:
switch (otherStatus) {
case Status.Active:
return 1;
case Status.Paused:
return 2;
case Status.Off:
return 3;
}
break;
}

switch case にブロックを追加できることを忘れないでください。ローカル変数を使用する場合に役立ちます

switch (status) {
case Status.Active: {
const x = f();
...
break;
}
case Status.Paused: {
const x = g();
...
break;
}
case Status.Off: {
const y = ...;
...
break;
}
}

この例でブロックを追加しなかった場合、`const x` の 2 つの宣言が競合し、エラーが発生します。

enum は、if ステートメントや switch ステートメント以外のコンテキストでは網羅的にチェックされません。

不明なメンバーを使用した網羅的なチェック

enum に不明なメンバー(`...` で指定)がある場合。例:

enum Status {
Active,
Paused,
Off,
...
}

次に、enum を切り替えるときは、常に `default` が必要です。`default` は、明示的にリストされていない「不明な」メンバーをチェックします。

switch (status) {
case Status.Active:
break;
case Status.Paused:
break;
case Status.Off:
break;
default:
// Checks for members not explicitly listed
}

すべての既知のメンバーがケースとして明示的にリストされるように要求するには、require-explicit-enum-switch-cases Flow Lintを使用できます。例えば

1enum Status {2  Active = 1,3  Paused = 2,4  Off = 3,5}6const status: Status = Status.Active;7
8// flowlint-next-line require-explicit-enum-switch-cases:error9switch (status) {
10 case Status.Active:11 break;12 case Status.Paused:13 break;14 default:15 break;16}
9:9-9:14: Incomplete exhaustive check: the member `Off` of enum `Status` [1] has not been considered in check of `status`. The default case [2] does not check for the missing members as the `require-explicit-enum-switch-cases` lint has been enabled. [require-explicit-enum-switch-cases]

次のように修正できます

// flowlint-next-line require-explicit-enum-switch-cases:error
switch (status) {
case Status.Active:
break;
case Status.Paused:
break;
case Status.Off: // Added the missing `Status.Off` case
break;
default:
break;
}

require-explicit-enum-switch-cases lint は、グローバルに有効にするのではなく、動作が必要な場合にスイッチごとに有効にする必要があります。通常の enum では、それに対する各 `switch` ステートメントで、`default` を指定するかどうかを選択できるため、各ケースを明示的にリストする必要があるかどうかを決定できます。同様に、不明なメンバーを持つ Flow enum の場合も、スイッチごとにこの lint を有効にできます。

この lint は、通常の Flow enum 型のスイッチでも機能します。実際には、すべての enum メンバーをケースとして明示的にリストすることで、その `switch` ステートメントでの `default` の使用を禁止します。

enum を他の値にマッピングする

enum 値を別の値(例:ラベル、アイコン、要素など)にマッピングしたい理由はさまざまです。

以前のパターンでは、この目的でオブジェクトリテラルを使用するのが一般的でしたが、Flow enum では、網羅的にチェックできる switch を含む関数を使用することをお勧めします。

次のかわりに

const STATUS_ICON: {[Status]: string} = {
[Status.Active]: 'green-checkmark',
[Status.Paused]: 'grey-pause',
[Status.Off]: 'red-x',
};
const icon = STATUS_ICON[status];

各 `Status` を何らかの値にマッピングしていることを実際には保証しないため、次を使用します

function getStatusIcon(status: Status): string {
switch (status) {
case Status.Active:
return 'green-checkmark';
case Status.Paused:
return 'grey-pause';
case Status.Off:
return 'red-x';
}
}
const icon = getStatusIcon(status);

将来、enum メンバーを追加または削除すると、Flow は switch も更新するように指示するので、常に正確です。

網羅的ではない辞書が必要な場合は、Map を使用できます

const counts = new Map<Status, number>([
[Status.Active, 2],
[Status.Off, 5],
]);
const activeCount: Status | void = counts.get(Status.Active);

Flow enum は、このページで後述するように、オブジェクトリテラルでキーとして使用することはできません。

union 内の enum

enum 値が union 内にある場合(例:`?Status`)、最初に enum 型のみに絞り込みます

const status: ?Status = ...;

if (status != null) {
status as Status; // 'status' is refined to 'Status' at this point
switch (status) {
case Status.Active: break;
case Status.Paused: break;
case Status.Off: break;
}
}

enum 値に絞り込む場合は、表現型で `typeof` を使用できます。例:

const val: Status | number = ...;

// 'Status' is a string enum
if (typeof val === 'string') {
val as Status; // 'val' is refined to 'Status' at this point
switch (val) {
case Status.Active: break;
case Status.Paused: break;
case Status.Off: break;
}
}

enum のエクスポート

enum は(クラスのような)型と値です。型と値の両方をエクスポートするには、値のようにエクスポートします。

1export enum Status {}

または、デフォルトのエクスポートとして(注:常に enum 名を指定する必要があります。`export default enum {}` は許可されていません)。

1export default enum Status {}

CommonJS の使用

1enum Status {}2module.exports = Status;

型のみをエクスポートし、値をエクスポートしない場合は、次のようにできます

1enum Status {}2export type {Status};

ファイル内の他の関数は、enum の実装に引き続きアクセスできます。

enum のインポート

次のように enum をエクスポートした場合

1// status.js2export default enum Status {3  Active,4  Paused,5  Off,6}

次のように、値と型の両方としてインポートできます。

import Status from 'status';

const x: Status /* used as type */ = Status.Active /* used as value */;

型のみを使用する必要がある場合は、型としてインポートできます。

import type Status from 'status';

function printStatus(status: Status) {
...
}

CommonJS の使用

const Status = require('status');

ジェネリック enum

現時点ではジェネリック enum 型を指定する方法はありませんが、多くの要望があるため、将来的には検討する予定です。

ジェネリック enum の一部のユースケースでは、現在、ユーザーに enum 自体を渡すのではなく、enum の メソッドを呼び出す関数を提供するように依頼することができます。たとえば、次のようになります。

function castToEnumArray<TRepresentationType, TEnum>(
f: TRepresentationType => TEnum,
xs: Array<TRepresentationType>,
): Array<TEnum | void> {
return xs.map(f);
}

castToEnumArray((input) => Status.cast(input), ["Active", "Paused", "Invalid"]);

enum を使用しない方が良い場合

enum は多くのユースケースをカバーし、特定の利点を示すように設計されています。この設計は、これを実現するためにさまざまなトレードオフを行っており、特定の状況では、これらのトレードオフが適切ではない場合があります。このような場合は、既存のパターンを引き続き使用して、ユースケースに対応できます。

異なるオブジェクトキー

enum のメンバーを異なるオブジェクトキーとして使用することはできません。

次のパターンが機能するのは、LegacyStatus.ActiveLegacyStatus.Offの型が異なるためです。一方は'Active'という型を持ち、もう一方は'Off'という型を持ちます。

1const LegacyStatus = Object.freeze({2  Active: 'Active',3  Paused: 'Paused',4  Off: 'Off',5});6const o = {7  [LegacyStatus.Active]: "hi",8  [LegacyStatus.Off]: 1,9};10const x: string = o[LegacyStatus.Active]; // OK11const y: number = o[LegacyStatus.Off]; // OK12const z: boolean = o[LegacyStatus.Active]; // Error - as expected
12:20-12:41: Cannot assign `o[LegacyStatus.Active]` to `z` because string [1] is incompatible with boolean [2]. [incompatible-type]

enum では同じパターンを使用できません。すべての enum メンバーは同じ型、つまり enum 型を持つため、Flow はキーと値の関係を追跡できません。

enum 値から別の値へのマッピングが必要な場合は、網羅的にチェックされた switch を使用した関数を使用する必要があります。

非交差オブジェクトユニオン

enum の決定的な特徴は、ユニオンとは異なり、各 enum メンバーが独自の独立した型を形成しないことです。すべてのメンバーは同じ型、つまり enum 型を持ちます。これにより、Flow による enum の使用状況を常に高速に分析できますが、個別の型が必要な特定の状況では、enum を使用できません。次のユニオンを、非交差オブジェクトユニオンパターンに従って考えてみましょう。

1type Action =2  | {type: 'Upload', data: string}3  | {type: 'Delete', id: number};

ユニオン内の各オブジェクト型には、どのオブジェクト型を処理しているかを区別するために使用される単一の共通フィールド(type)があります。

このメカニズムが機能するためには、そのフィールドの型がユニオンの各メンバーで異なる必要がありますが、enum メンバーはすべて同じ型を持つため、このフィールドに enum 型を使用することはできません。

将来的には、キーとプリミティブ値に加えて追加のデータをカプセル化する enum の機能を追加する可能性があります。これにより、非交差オブジェクトユニオンを置き換えることができるようになります。

保証されたインライン化

Flow Enum はインライン化を可能にするように設計されています(例:メンバー値はリテラルでなければならないenum は固定されている)。ただし、インライン化自体は、Flow 自体ではなく、(使用している)ビルドシステムの一部である必要があります。

enum メンバーアクセス(例:Status.Active)はインライン化できますが(シンボル enumはシンボルの性質上インライン化できません)、そのメソッドの使用(例:Status.cast(x))はインライン化できません。