[enums] 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컴파일러 플래그 도 있습니다. 이것이 이것들과 어떻게 상호 작용합니까?



답변

주의해야 할 TypeScript의 열거 형에는 네 가지 측면이 있습니다. 첫째, 몇 가지 정의 :

“조회 개체”

이 열거 형을 작성하면 :

enum Foo { X, Y }

TypeScript는 다음 개체를 내 보냅니다.

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

이것을 조회 객체라고 부를 것 입니다. 그 목적은 두 가지이다 :에서 매핑 역할을 문자열번호를 기록 할 때 예를 들어, Foo.XFoo['X'], 그리고에서 매핑 역할을 숫자 문자열 . 이 역 매핑은 디버깅 또는 로깅 목적에 유용합니다. 종종 값이 0있거나 1해당 문자열 "X"또는 "Y".

“선언” 또는 ” 주변

TypeScript에서는 컴파일러가 알아야하는 것을 “선언”할 수 있지만 실제로 코드를 내보내지는 않습니다. 이것은 일부 객체를 정의하는 jQuery와 같은 라이브러리가있을 때 유용합니다 (예 :$ 타입 정보를 원하지만 컴파일러에 의해 생성 된 코드가 필요하지 않은 . 사양 및 기타 문서에서는 이러한 방식으로 작성된 선언을 “주변”컨텍스트에있는 것으로 언급합니다. .d.ts파일의 모든 선언 이 “주변” 이라는 점에 유의하는 것이 중요합니다 ( declare선언 유형에 따라 명시 적 수정자가 필요 하거나 암시 적으로 포함됨).

“인라이닝”

성능 및 코드 크기 때문에 컴파일 할 때 열거 형 멤버에 대한 참조를 해당 숫자로 대체하는 것이 선호되는 경우가 많습니다.

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

사양에서는이 대체 라고 부르며, 더 멋지게 들리기 때문에 인라인 이라고합니다. 열거 형 멤버가 인라인되는 것을 원하지 않는 경우가 있습니다. 예를 들어 향후 API 버전에서 열거 형 값이 변경 될 수 있기 때문입니다.


열거 형, 어떻게 작동합니까?

열거 형의 각 측면으로 이것을 분해 해 보겠습니다. 안타깝게도이 네 섹션 각각은 다른 모든 섹션의 용어를 참조하므로이 전체를 한 번 이상 읽어야 할 것입니다.

계산 됨 vs 계산되지 않음 (상수)

열거 형 멤버는 계산 되거나 계산 되지 않을 수 있습니다. 사양은 계산되지 않은 멤버 상수를 호출하지만 const 와의 혼동을 피하기 위해 계산되지 않은 멤버 라고 부를 것입니다. .

계산 ENUM 부재는 그 값이 컴파일시에 알려져 있지 하나이다. 물론 계산 된 멤버에 대한 참조는 인라인 될 수 없습니다. 반대로 계산되지 않은 열거 형 멤버는 값 컴파일 타임에 알려진 . 계산되지 않은 멤버에 대한 참조는 항상 인라인됩니다.

계산되는 열거 형 멤버와 계산되지 않는 멤버는 무엇입니까? 첫째, const열거 형 의 모든 멤버 는 이름에서 알 수 있듯이 상수 (즉, 계산되지 않음)입니다. 상수가 아닌 열거 형의 경우 주변 (선언) 열거 형을 보고 있는지 아니면 주변 이 아닌 열거 형을 보고 있는지에 따라 다릅니다 .

a의 멤버 declare enum(즉, 앰비언트 열거 형)는 이니셜 라이저가있는 경우에만 상수 입니다. 그렇지 않으면 계산됩니다. A의 참고 declare enum숫자 만 초기화가 허용됩니다. 예:

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

마지막으로 비 선언 non-const 열거 형의 멤버는 항상 계산 된 것으로 간주됩니다. 그러나 초기화 표현식은 컴파일 타임에 계산할 수있는 경우 상수로 축소됩니다. 즉, 상수가 아닌 열거 형 멤버는 인라인되지 않습니다 (이 동작은 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 열거 형은 컴파일 될 때 조회 개체를 생성하지 않습니다. Foo따라서 멤버 참조의 일부를 제외하고 위 코드에서 참조하는 것은 오류 입니다. 아니Foo런타임에는 개체가 .

상수가 아닌

열거 형 선언에 const수정자가 없으면 해당 멤버에 대한 참조는 멤버가 계산되지 않은 경우에만 인라인됩니다. 상수가 아닌 선언되지 않은 열거 형은 조회 개체를 생성합니다.

선언 (주변) 대 비 선언

중요한 서문은 declareTypeScript에서 매우 구체적인 의미 가 있다는 것 입니다. 이 객체는 다른 곳에 존재합니다 . 기존 객체 를 설명하기위한 것 입니다. declare실제로 존재하지 않는 객체를 정의하는 데 사용하면 나쁜 결과를 초래할 수 있습니다. 나중에 살펴 보겠습니다.

알리다

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 enumconst enum–preserveConstEnums의 경우를 제외하고는 a 와 다르지 않습니다 (아래 참조).

비 선언

선언되지 않은 열거 형은 그렇지 않은 경우 조회 개체를 생성합니다 const. 인라이닝은 위에 설명되어 있습니다.

–preserveConstEnums 플래그

이 플래그는 정확히 한 가지 효과가 있습니다. 선언되지 않은 const 열거 형은 조회 객체를 내 보냅니다. 인라이닝은 영향을받지 않습니다. 이것은 디버깅에 유용합니다.


일반적인 오류

가장 일반적인 실수는 사용하는 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 참조 ) 이 변경되었습니다. 리터럴로 명시 적으로 초기화됩니다. 즉,이 “아기 분리”는 인라인 동작을보다 예측 가능하게 만들고 const enum일반 에서 개념을보다 명확하게 분리합니다 enum. 이 변경 이전에는 non-const 열거 형의 계산되지 않은 멤버가 더 적극적으로 인라인되었습니다.


답변

여기에는 몇 가지 일이 있습니다. 사례별로 가자.

열거 형

enum Cheese { Brie, Cheddar }

첫째, 평범한 오래된 열거 형. JavaScript로 컴파일하면 조회 테이블이 생성됩니다.

조회 테이블은 다음과 같습니다.

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

그런 다음 Cheese.BrieTypeScript에 있으면 Cheese.Brie0으로 평가되는 JavaScript에서 Cheese[0]방출 됩니다. 방출 Cheese[0]하고 실제로 평가합니다."Brie" .

const 열거 형

const enum Bread { Rye, Wheat }

이를 위해 실제로 코드가 생성되지 않습니다! 값은 인라인됩니다. 다음은 JavaScript에서 값 0 자체를 내 보냅니다.

Bread.Rye
Bread['Rye']

const enums ‘인라인은 성능상의 이유로 유용 할 수 있습니다.

하지만 Bread[0]어떨까요? 이것은 런타임에 오류가 발생하고 컴파일러가이를 포착해야합니다. 조회 테이블이없고 컴파일러가 여기에 인라인되지 않습니다.

위의 경우 –preserveConstEnums 플래그는 Bread가 조회 테이블을 내보내도록합니다. 그 값은 여전히 ​​인라인됩니다.

열거 형 선언

의 다른 용도와 마찬가지로 declare, declare어떤 코드를 방출하지 않고 다른 곳에서 실제 코드를 정의한 것으로 기대하고있다. 이것은 조회 테이블을 생성하지 않습니다.

declare enum Wine { Red, Wine }

Wine.Red 방출 Wine.RedJavaScript에서 하지만 참조 할 Wine 조회 테이블이 없으므로 다른 곳에서 정의하지 않는 한 오류입니다.

const enum 선언

이것은 조회 테이블을 생성하지 않습니다.

declare const enum Fruit { Apple, Pear }

그러나 그것은 인라인입니다! Fruit.Apple0을 방출합니다. 그러나 Fruit[0]인라인되지 않고 조회 테이블이 없기 때문에 런타임에 다시 오류가 발생합니다.

놀이터 에 이것을 적었습니다. 어떤 TypeScript가 어떤 JavaScript를 방출하는지 이해하기 위해 그곳에서 플레이하는 것이 좋습니다.


답변