web-dev-qa-db-ja.com

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

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コンパイラフラグもあります。これはこれらとどのように相互作用しますか?

83
Ryan Cavanaugh

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

"ルックアップオブジェクト"

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

enum Foo { X, Y }

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

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

これをlookup objectと呼びます。その目的は2つあります。stringsからnumbersへのマッピングとして機能すること。 Foo.XまたはFoo['X']を記述し、numbersからstringsへのマッピングとして機能する場合。この逆マッピングは、デバッグやログ記録の目的に役立ちます。多くの場合、値は0または1になり、対応する文字列"X"または"Y"を取得します。

"declare"または "ambient"

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

"インライン化"

パフォーマンスとコードサイズの理由から、コンパイル時に列挙メンバーへの参照を同等の数値に置き換えることが望ましい場合がよくあります。

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

仕様ではこれをsubstitutionと呼びますが、私はinliningと呼びます。たとえば、enum値はAPIの将来のバージョンで変更される可能性があるため、enumメンバーをインライン化したい場合はnotにします。


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

これを列挙型の各側面で分類しましょう。残念ながら、これら4つのセクションはそれぞれ、他のすべてのセクションから用語を参照するため、おそらくこの記事全体を複数回読む必要があります。

計算済みvs非計算済み(定数)

列挙型のメンバーは、computedのいずれかです。仕様は非計算メンバーconstantを呼び出しますが、constとの混乱を避けるためにnon-computedと呼び出します。

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

どの列挙メンバーが計算され、どれが計算されませんか?最初に、const列挙型のすべてのメンバーは、名前が示すように定数(つまり、計算されていない)です。非const列挙型の場合、ambient(宣言)列挙型か非周囲型列挙型のどちらを表示しているかによって異なります。

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

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

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

constと非const

const

Enum宣言には、const修飾子を含めることができます。列挙型がconstの場合、allのメンバーへの参照がインライン化されます。

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

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

非const

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

宣言(アンビエント)vs非宣言

重要な序文は、TypeScriptのdeclareには非常に具体的な意味があることです。このオブジェクトは、別の場所に存在しますexistingオブジェクトを記述するためのものです。 declareを使用して実際に存在しないオブジェクトを定義すると、悪い結果を招く可能性があります。これらについては後で説明します。

宣言する

declare enumはルックアップオブジェクトを発行しません。それらのメンバーが計算される場合、そのメンバーへの参照はインライン化されます(上記の計算済みと非計算済みを参照)。

declare enumareへの他の参照形式が許可されていることに注意することが重要です。このコードはnotコンパイルエラーですが、willは実行時に失敗します。

// 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を記述しないでください!

declare const enumconst enumと違いはありませんが、-preserveConstEnumsの場合を除きます(以下を参照)。

非宣言

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

--preserveConstEnumsフラグ

このフラグには、1つの効果があります。非宣言const列挙型は、ルックアップオブジェクトを出力します。インライン化は影響を受けません。これはデバッグに役立ちます。


一般的なエラー

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

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]);
}

黄金のルールを覚えておいてください:Never declare実際には存在しないもの。常にインライン化する場合はconst enumを使用し、ルックアップオブジェクトが必要な場合はenumを使用します。


TypeScriptの変更

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

195
Ryan Cavanaugh

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

列挙型

_enum Cheese { Brie, Cheddar }
_

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

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

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

次に、TypeScriptに_Cheese.Brie_があると、0に評価されるJavaScriptで_Cheese.Brie_を出力します。_Cheese[0]_は_Cheese[0]_を出力し、実際に_"Brie"_に評価されます。

const enum

_const enum Bread { Rye, Wheat }
_

このために実際にコードは出力されません!その値はインライン化されます。以下は、JavaScriptで値0自体を出力します。

_Bread.Rye
Bread['Rye']
_

_const enum_ sのインライン化は、パフォーマンス上の理由で役立つ場合があります。

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

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

列挙型を宣言する

declareの他の用途と同様に、declareはコードを出力せず、実際のコードを他の場所で定義したことを期待します。これはルックアップテーブルを出力しません。

_declare enum Wine { Red, Wine }
_

_Wine.Red_はJavaScriptで_Wine.Red_を出力しますが、参照するWineルックアップテーブルがないため、他で定義しない限りエラーになります。

const enumを宣言します

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

_declare const enum Fruit { Apple, Pear }
_

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

これは this playgroundで書きました。どのTypeScriptがどのJavaScriptを出力するかを理解するために、そこでプレイすることをお勧めします。

17
Kat