TypeScriptでは、さまざまな列挙型バリアントはどのように機能しますか?


116

TypeScriptには、列挙型を定義するさまざまな方法があります。

enum Alpha { X, Y, Z }
const enum Beta { X, Y, Z }
declare enum Gamma { X, Y, Z }
declare const enum Delta { X, Y, Z }

Gamma実行時にから値を使用しようとするとGamma、が定義されていないためエラーが発生しますが、Deltaまたはの場合はそうではありませんAlpha。何をしないconstか、declareここに宣言に意味ですか?

preserveConstEnumsコンパイラフラグもあります-これはこれらとどのように相互作用しますか?


1
これについての記事を書いたところですが、 constと非const列挙型の比較に多くの関係があります
joelmdev

回答:


246

TypeScriptの列挙型には、注意する必要がある4つの異なる側面があります。まず、いくつかの定義:

「ルックアップオブジェクト」

この列挙型を記述する場合:

enum Foo { X, Y }

TypeScriptは次のオブジェクトを発行します。

var Foo;
(function (Foo) {
    Foo[Foo["X"] = 0] = "X";
    Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));

これをルックアップオブジェクトと呼びます。その目的は2つあり:からのマッピングとして機能するように、文字列数字書くときなど、Foo.XまたはFoo['X']、およびからのマッピングとして機能するように数値文字列。この逆マッピングは、デバッグやロギングの目的で役立ちます。多くの場合、値を持っている01、対応する文字列"X"or を取得したいでしょう"Y"

「宣言」または「周囲

TypeScriptでは、コンパイラが知っておくべきことを "宣言"できますが、実際にはコードを発行しません。これは$、型情報が必要なオブジェクト(など)を定義するjQueryのようなライブラリーがあるが、コンパイラーによって作成されたコードは必要ない場合に役立ちます。仕様およびその他のドキュメントでは、このようにして作成された宣言を「周囲の」状況にあるものとして参照しています。.d.tsファイル内のすべての宣言は「アンビエント」であることに注意することが重要です(declare宣言タイプに応じて、明示的な修飾子を必要とするか、暗黙的に修飾するか)。

「インライン化」

パフォーマンスとコードサイズの理由から、多くの場合、コンパイル時にenumメンバーへの参照を同等の数値で置き換えることが推奨されます。

enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";

仕様ではこの置換と呼ばれていますが、音が涼しいのでインライン化と呼びます。たとえば、enum値がAPIの将来のバージョンで変更される可能性があるため、enumメンバーをインライン化したくない場合があります。


列挙型、それらはどのように機能しますか?

列挙型の各側面でこれを分解してみましょう。残念ながら、これらの4つのセクションはそれぞれ、他のすべての用語を参照するため、この全体を何度も読む必要があるでしょう。

計算済みvs計算なし(定数)

列挙型メンバーは、計算される場合とされない場合があります。仕様では、計算されていないメンバーは定数と呼ばれていますが、constとの混同を避けるために、それらは計算されていないメンバーと呼びます。

計算された列挙体は、その値がコンパイル時に知られていないものです。もちろん、計算されたメンバーへの参照はインライン化できません。逆に、計算されていない列挙型メンバーは、その値コンパイル時にわかっているものです。非計算メンバーへの参照は常にインライン化されます。

計算される列挙メンバーと計算されない列挙メンバーはどれですか?まず、const名前が示すように、列挙型のすべてのメンバーは定数です(つまり、計算されません)。非const列挙型の場合は、アンビエント(宣言)列挙型と非アンビエント列挙型のどちらを見ているかによって異なります。

a declare enum(つまり、環境列挙型)のメンバーは、初期化子がある場合限り、定数です。それ以外の場合は、計算されます。declare enumでは、数値初期化子のみが許可されていることに注意してください。例:

declare enum Foo {
    X, // Computed
    Y = 2, // Non-computed
    Z, // Computed! Not 3! Careful!
    Q = 1 + 1 // Error
}

最後に、非宣言および非定数の列挙型のメンバーは常に計算されたと見なされます。ただし、初期化式は、コンパイル時に計算可能な場合は定数に縮小されます。これは、非const enumメンバーがインライン化されないことを意味します(この動作はTypeScript 1.5で変更されました。下部の「TypeScriptの変更」を参照してください)

constとnon-const

const

列挙型宣言はconst修飾子を持つことができます。列挙型である場合はconstすべてのメンバーへの参照がインライン化。

const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always

const enumは、コンパイル時にルックアップオブジェクトを生成しません。このため、Fooメンバー参照の一部として以外は、上記のコードで参照するとエラーになります。Foo実行時にオブジェクトは存在しません。

非定数

enum宣言にconst修飾子がない場合、そのメンバーへの参照は、メンバーが計算されていない場合にのみインライン化されます。非const、非宣言の列挙型は、ルックアップオブジェクトを生成します。

宣言(周囲)対非宣言

重要な序文はdeclare、TypeScriptには非常に特定の意味があるということですこのオブジェクトはどこかに存在します既存のオブジェクトを説明するためものです。declare実際に存在しないオブジェクトを定義するためにを使用すると、悪影響が生じる可能性があります。これらについては後で説明します。

宣言する

Aは、declare enum検索対象を放出しません。それらのメンバーが計算される場合、そのメンバーへの参照はインライン化されます(計算されたものと計算されていないものについては上記を参照)。

これは、参照のこと他の形式に注意することが重要ですdeclare enum され、例えば、このコードは許されないコンパイルエラーはなくなり、実行時に失敗します。

// Note: Assume no other file has actually created a Foo var at runtime
declare enum Foo { Bar } 
var s = 'Bar';
var b = Foo[s]; // Fails

このエラーは、「コンパイラに嘘をつかない」のカテゴリに分類されます。Foo実行時に名前が付けられたオブジェクトがない場合は、記述しないでくださいdeclare enum Foo

Aはdeclare const enum異ならないconst enum(下記参照)--preserveConstEnumsの場合を除き、。

宣言しない

非宣言列挙は、そうでない場合、ルックアップオブジェクトを生成しますconst。インライン化については上で説明しています。

--preserveConstEnumsフラグ

このフラグの効果は1つだけです。非宣言のconst enumはルックアップオブジェクトを生成します。インライン化は影響を受けません。これはデバッグに役立ちます。


一般的なエラー

最も一般的な間違いはdeclare enum、通常enumまたはconst enumより適切な場合にa を使用することです。一般的な形式は次のとおりです。

module MyModule {
    // Claiming this enum exists with 'declare', but it doesn't...
    export declare enum Lies {
        Foo = 0,
        Bar = 1     
    }
    var x = Lies.Foo; // Depend on inlining
}

module SomeOtherCode {
    // x ends up as 'undefined' at runtime
    import x = MyModule.Lies;

    // Try to use lookup object, which ought to exist
    // runtime error, canot read property 0 of undefined
    console.log(x[x.Foo]);
}

黄金律を覚えておいてください:実際に存在しないものは決してdeclareありません。使用const enumあなたは常にインライン化したい場合、またはenumあなたが検索対象にしたい場合。


TypeScriptの変更

TypeScript 1.4と1.5の間で、動作が変更され(https://github.com/Microsoft/TypeScript/issues/2183を参照)、宣言されていない非const列挙型のすべてのメンバーが計算されたものとして扱われるようになりました。それらはリテラルで明示的に初期化されます。これは、いわば「赤ちゃんを分割しない」ことであり、インライン動作をより予測可能にし、の概念をconst enum通常のからより明確に分離しenumます。この変更以前は、非const enumの非計算メンバーはより積極的にインライン化されていました。


6
本当に素晴らしい答えです。列挙型だけでなく、私にとって非常に多くのことを解決しました。
クラーク

1
私はあなたに複数回投票できればいいのですが...その重大な変化については知りませんでした。適切なセマンティックバージョニングでは、これはメジャーバージョンへのバンプと考えることができます:-/
mfeineis

さまざまなenumタイプの非常に役立つ比較、ありがとうございます!
Marius Schulz

@ライアンこれはとても役に立ちます、ありがとう!宣言された列挙型に適切なものを生成するには、Web Essentials 2015が必要constです。
15

19
この答えは、1.4の状況を非常に詳しく説明しているように見え、最後に「1.5ではすべてが変更され、今でははるかに簡単になりました」と書かれています。私は物事を正しく理解していると仮定すると、この回答は古くなるにつれて、この組織はますます不適切になります。単純な現在の状況を最初に置き、その後に「1.4以前のバージョンを使用している場合は、もう少し複雑です。」
KRyan

33

ここでいくつかのことが起こっています。ケースバイケースで行きましょう。

列挙型

enum Cheese { Brie, Cheddar }

まず、プレーンな古い列挙型。JavaScriptにコンパイルすると、ルックアップテーブルが生成されます。

ルックアップテーブルは次のようになります。

var Cheese;
(function (Cheese) {
    Cheese[Cheese["Brie"] = 0] = "Brie";
    Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));

あなたが持っているときにCheese.Brie活字体で、それが発するCheese.Brie0と評価されたJavaScriptでCheese[0]発光するCheese[0]と、実際に評価します"Brie"

const enum

const enum Bread { Rye, Wheat }

これに対して実際にコードは発行されません!その値はインライン化されています。以下は、JavaScriptで値0自体を発行します。

Bread.Rye
Bread['Rye']

const enumsのインライン化は、パフォーマンス上の理由から役立つ場合があります。

しかし、どうBread[0]ですか?これは実行時にエラーになり、コンパイラはそれをキャッチする必要があります。ルックアップテーブルはなく、コンパイラーはここにインライン化しません。

上記の場合、-preserveConstEnumsフラグによってBreadがルックアップテーブルを生成することに注意してください。ただし、その値はインライン化されます。

列挙型を宣言する

の他の使用方法と同様にdeclaredeclareはコードを発行せず、実際のコードを他の場所で定義したことを期待します。これはルックアップテーブルを発行しません。

declare enum Wine { Red, Wine }

Wine.RedWine.RedJavaScriptで発行されますが、参照するWineルックアップテーブルはないため、他で定義していない限り、エラーになります。

const enumを宣言する

これはルックアップテーブルを発行しません。

declare const enum Fruit { Apple, Pear }

しかし、それはインラインです!Fruit.Apple0をFruit[0]発行します。ただし、インライン化されておらず、ルックアップテーブルがないため、実行時にエラーになります。

これをこの遊び場で書いた。どのTypeScriptがどのJavaScriptを出力するかを理解するために、そこでプレイすることをお勧めします。


1
この回答を更新することをお勧めします。Typescript3.3.3以降Bread[0]、コンパイラエラーをスローします。「const enumメンバーには文字列リテラルを使用してのみアクセスできます。」
chharvey

1
うーん...それは答えが言うこととは違うのですか?「しかし、Bread [0]についてはどうですか?これは実行時にエラーとなり、コンパイラはそれをキャッチするはずです。ルックアップテーブルがなく、コンパイラはここにインライン化しません。」
Kat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.