本文介绍了不同的枚举变体如何在 TypeScript 中工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TypeScript 有很多不同的方式来定义枚举:

TypeScript has a bunch of different ways to define an 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?constdeclare 在此处的声明中是什么意思?

If I try to use a value from Gamma at runtime, I get an error because Gamma is not defined, but that's not the case for Delta or Alpha? What does const or declare mean on the declarations here?

还有一个 preserveConstEnums 编译器标志——它如何与这些交互?

There's also a preserveConstEnums compiler flag -- how does this interact with these?

推荐答案

您需要注意 TypeScript 中枚举的四个不同方面.首先,一些定义:

There are four different aspects to enums in TypeScript you need to be aware of. First, some definitions:

如果你写这个枚举:

enum Foo { X, Y }

TypeScript 将发出以下对象:

TypeScript will emit the following object:

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

我将其称为查找对象.它的目的是双重的:作为从 stringsnumbers 的映射,例如在编写 Foo.XFoo['X'] 时,用作从数字字符串.该反向映射对于调试或日志记录很有用——您通常会拥有值 01 并希望获得相应的字符串 "X""Y".

I'll refer to this as the lookup object. Its purpose is twofold: to serve as a mapping from strings to numbers, e.g. when writing Foo.X or Foo['X'], and to serve as a mapping from numbers to strings. That reverse mapping is useful for debugging or logging purposes -- you will often have the value 0 or 1 and want to get the corresponding string "X" or "Y".

在 TypeScript 中,您可以声明"编译器应该知道的事情,但实际上不会为其发出代码.当你有像 jQuery 这样的库定义了一些你想要类型信息的对象(例如 $),但不需要编译器创建的任何代码时,这很有用.规范和其他文档将这种方式的声明称为在环境"上下文中;重要的是要注意 .d.ts 文件中的所有声明都是环境"的(要么需要显式的 declare 修饰符,要么隐式地使用它,具体取决于声明类型).

In TypeScript, you can "declare" things that the compiler should know about, but not actually emit code for. This is useful when you have libraries like jQuery that define some object (e.g. $) that you want type information about, but don't need any code created by the compiler. The spec and other documentation refers to declarations made this way as being in an "ambient" context; it is important to note that all declarations in a .d.ts file are "ambient" (either requiring an explicit declare modifier or having it implicitly, depending on the declaration type).

出于性能和代码大小的原因,通常最好在编译时将枚举成员的引用替换为其等效的数字:

For performance and code size reasons, it's often preferable to have a reference to an enum member replaced by its numeric equivalent when compiled:

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

规范称之为替代,我称之为内联,因为它听起来更酷.有时您希望枚举成员被内联,例如因为枚举值可能会在 API 的未来版本中发生变化.

The spec calls this substitution, I will call it inlining because it sounds cooler. Sometimes you will not want enum members to be inlined, for example because the enum value might change in a future version of the API.

让我们通过枚举的每个方面来分解它.不幸的是,这四个部分中的每一个都将引用所有其他部分的术语,因此您可能需要不止一次阅读整篇文章.

Let's break this down by each aspect of an enum. Unfortunately, each of these four sections is going to reference terms from all of the others, so you'll probably need to read this whole thing more than once.

枚举成员可以被计算,也可以不被计算.规范将非计算成员称为 constant,但我将它们称为 非计算 以避免与 const 混淆.

Enum members can either be computed or not. The spec calls non-computed members constant, but I'll call them non-computed to avoid confusion with const.

计算 枚举成员是一个其值在编译时未知的成员.当然,对计算成员的引用不能内联.相反,非计算枚举成员的值在编译时已知的.对非计算成员的引用始终内联.

A computed enum member is one whose value is not known at compile-time. References to computed members cannot be inlined, of course. Conversely, a non-computed enum member is once whose value is known at compile-time. References to non-computed members are always inlined.

哪些枚举成员是计算的,哪些是非计算的?首先,顾名思义, const 枚举的所有成员都是常量(即非计算).对于非常量枚举,这取决于您查看的是环境(声明)枚举还是非环境枚举.

Which enum members are computed and which are non-computed? First, all members of a const enum are constant (i.e. non-computed), as the name implies. For a non-const enum, it depends on whether you're looking at an ambient (declare) enum or a non-ambient enum.

declare enum(即环境枚举)的成员是常量当且仅当它有一个初始化器.否则,它被计算.请注意,在 declare enum 中,只允许使用数字初始值设定项.示例:

A member of a declare enum (i.e. ambient enum) is constant if and only if it has an initializer. Otherwise, it is computed. Note that in a declare enum, only numeric initializers are allowed. Example:

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

最后,非声明非常量枚举的成员总是被认为是计算过的.但是,如果它们在编译时是可计算的,则它们的初始化表达式将减少为常量.这意味着非常量枚举成员永远不会内联(这种行为在 TypeScript 1.5 中发生了变化,请参阅底部的TypeScript 中的变化")

Finally, members of non-declare non-const enums are always considered to be computed. However, their initializing expressions are reduced down to constants if they're computable at compile-time. This means non-const enum members are never inlined (this behavior changed in TypeScript 1.5, see "Changes in TypeScript" at the bottom)

枚举声明可以有 const 修饰符.如果枚举是 const,则所有 对其成员的引用都内联.

An enum declaration can have the const modifier. If an enum is const, all references to its members inlined.

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

const 枚举在编译时不会产生查找对象.因此,在上述代码中引用 Foo 是错误的,除非作为成员引用的一部分.运行时不会出现 Foo 对象.

const enums do not produce a lookup object when compiled. For this reason, it is an error to reference Foo in the above code except as part of a member reference. No Foo object will be present at runtime.

如果枚举声明没有 const 修饰符,则仅当该成员未计算时才内联对其成员的引用.一个非常量、非声明的枚举将产生一个查找对象.

If an enum declaration does not have the const modifier, references to its members are inlined only if the member is non-computed. A non-const, non-declare enum will produce a lookup object.

一个重要的前言是,TypeScript 中的declare 有一个非常具体的含义:这个对象存在于别处.它用于描述现有对象.使用 declare 定义实际上并不存在的对象可能会产生不良后果;我们稍后会探讨这些.

An important preface is that declare in TypeScript has a very specific meaning: This object exists somewhere else. It's for describing existing objects. Using declare to define objects that don't actually exist can have bad consequences; we'll explore those later.

declare enum 不会发出查找对象.如果这些成员被计算,则对其成员的引用被内联(参见上文关于计算与非计算).

A declare enum will not emit a lookup object. References to its members are inlined if those members are computed (see above on computed vs non-computed).

重要的是要注意对 declare enum 的其他引用形式是允许的,例如此代码不是编译错误,但在运行时失败:

It's important to note that other forms of reference to a declare enum are allowed, e.g. this code is not a compile error but will fail at runtime:

// 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

This error falls under the category of "Don't lie to the compiler". If you don't have an object named Foo at runtime, don't write declare enum Foo!

declare const enumconst enum 没有区别,除了 --preserveConstEnums(见下文).

A declare const enum is not different from a const enum, except in the case of --preserveConstEnums (see below).

如果非声明枚举不是const,则会生成一个查找对象.内联如上所述.

A non-declare enum produces a lookup object if it is not const. Inlining is described above.

这个标志只有一个效果:未声明的 const 枚举将发出一个查找对象.内联不受影响.这对调试很有用.

This flag has exactly one effect: non-declare const enums will emit a lookup object. Inlining is not affected. This is useful for debugging.

最常见的错误是使用 declare enum 当常规 enumconst enum 更合适时.一个常见的形式是这样的:

The most common mistake is to use a declare enum when a regular enum or const enum would be more appropriate. A common form is this:

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

记住黄金法则:永远不要声明实际上不存在的东西.如果您总是想要内联,请使用 const enum,如果您想要查找对象,请使用 enum.

Remember the golden rule: Never declare things that don't actually exist. Use const enum if you always want inlining, or enum if you want the lookup object.

在 TypeScript 1.4 和 1.5 之间,行为发生了变化(参见 https://github.com/Microsoft/TypeScript/issues/2183) 使非声明非常量枚举的所有成员都被视为计算过的,即使它们是用文字显式初始化的.可以这么说,这种unsplit the baby"使得内联行为更可预测,并且更清晰地将const enum 的概念与常规enum 分开.在此更改之前,非常量枚举的非计算成员被更积极地内联.

Between TypeScript 1.4 and 1.5, there was a change in the behavior (see https://github.com/Microsoft/TypeScript/issues/2183) to make all members of non-declare non-const enums be treated as computed, even if they're explicitly initialized with a literal. This "unsplit the baby", so to speak, making the inlining behavior more predictable and more cleanly separating the concept of const enum from regular enum. Prior to this change, non-computed members of non-const enums were inlined more aggressively.

这篇关于不同的枚举变体如何在 TypeScript 中工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 14:29