1. ホーム
  2. enums

[解決済み] TypeScriptでenumのバリアントはどのように動作するのですか?

2022-07-16 21:08:58

質問

TypeScriptには、enumを定義するためのさまざまな方法があります。

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 というコンパイラフラグがありますが、これとどのような関係があるのでしょうか?

どのように解決するのですか?

TypeScriptのenumには4つの側面があり、注意する必要があります。まず、いくつかの定義です。

lookup object"。

このenumを書くと

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'] からのマッピングとして機能します。 数字 文字列 . この逆マッピングはデバッグやロギングを行う際に便利です。 0 または 1 で、対応する文字列を取得したい場合 "X" または "Y" .

"declare"。 または" アンビエント "です。

TypeScriptでは、コンパイラが知るべきものでありながら、実際にはコードを出力しないものを"declare"することができます。これは、jQueryのような、あるオブジェクトを定義するライブラリ(例えば $ など)を定義しているjQueryのようなライブラリで、型情報は欲しいがコンパイラが作成するコードは必要ない場合に有効だ。仕様書や他の文書では、この方法で宣言されたものをアンビエントコンテキストと呼んでいます。 .d.ts ファイル内のすべての宣言は "ambient"であることに注意してください (明示的な declare 修飾子を必要とするか、暗黙的に持つか、宣言の種類によって異なります)。

"インライン化"。

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

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

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


列挙型はどのように機能するのか?

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

計算されたものとそうでないもの (定数)

Enumのメンバは、以下のいずれかになります。 計算される またはそうでないものがあります。仕様では、計算されないメンバを 定数 と呼んでいますが、ここでは 非計算 との混同を避けるために const .

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

どのenumのメンバが計算され、どれが計算されないのでしょうか?まず const enum の全てのメンバは、その名前が示すように定数 (すなわち、非計算) です。定数でない列挙型の場合、それは アンビエント (宣言) enum を見ているか、非 ambient enum を見ているかによります。

のメンバは declare enum (すなわちアンビエントな列挙型) は、定数 の場合のみ の場合のみ定数であり、イニシャライザを持ちます。それ以外の場合は、計算されます。注意点として declare enum では、数値のイニシャライザのみが許可されることに注意してください。例

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

最後に、non-declare nononst enums のメンバは常に計算されるものとみなされます。しかし、コンパイル時に計算可能であれば、その初期化式は定数に還元される。つまり、non-const enumのメンバは決してインライン化されない(この動作はTypeScript 1.5で変更されたため、最下部の"TypeScriptの変更点"を参照)。

constとnon-const

定数

enum宣言には const 修飾子があります。もしenumが const , すべて への参照はインライン化されています。

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

const enum はコンパイル時にルックアップ・オブジェクトを生成しません。このため、定数型列挙体を参照することはエラーとなります。 Foo を参照することはエラーです。いいえ Foo オブジェクトは実行時に存在しません。

非制約

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

declare (ambient) vs non-declare(アンビエント宣言)。

重要な前置きとして declare は非常に特殊な意味を持つということです。 このオブジェクトは他の場所に存在する . を記述するためのものです。 存在する オブジェクトを記述するためのものです。使用方法 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 の場合を除きます(下記参照)。

非宣言

non-declare enum は、ルックアップ・オブジェクトを生成する場合、それが const . インライン化については前述の通りです。

--preserveConstEnums フラグ

このフラグの効果はただ一つ: 非宣言の const enums はルックアップ・オブジェクトを生成します。インライン化は影響を受けません。これはデバッグに便利です。


よくあるエラー

最もよくある間違いは declare enum を使うことです。 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]);
}

黄金律を忘れるな 決して declare 実際に存在しないもの . 使用する const enum を、常にインライン化したい場合は enum で、ルックアップ・オブジェクトが必要なら


TypeScriptの変更点

TypeScript 1.4と1.5の間で、動作に変更がありました( https://github.com/Microsoft/TypeScript/issues/2183 を参照)、リテラルで明示的に初期化されていても、宣言されていないnon-const enumのメンバーはすべてcomputedとして扱われるようになった。これはいわば、インライン化の挙動をより予測しやすくし、よりきれいに const enum と通常の enum . この変更以前は、非構成式列挙型の計算されないメンバはより積極的にインライン化されていました。