Typescript перечисление возможных значений

Enums

Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript.

Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.

We’ll first start off with numeric enums, which are probably more familiar if you’re coming from other languages. An enum can be defined using the enum keyword.

Above, we have a numeric enum where Up is initialized with 1 . All of the following members are auto-incremented from that point on. In other words, Direction.Up has the value 1 , Down has 2 , Left has 3 , and Right has 4 .

If we wanted, we could leave off the initializers entirely:

Here, Up would have the value 0 , Down would have 1 , etc. This auto-incrementing behavior is useful for cases where we might not care about the member values themselves, but do care that each value is distinct from other values in the same enum.

Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum:

Numeric enums can be mixed in computed and constant members (see below). The short story is, enums without initializers either need to be first, or have to come after numeric enums initialized with numeric constants or other constant enum members. In other words, the following isn’t allowed:

String enums are a similar concept, but have some subtle runtime differences as documented below. In a string enum, each member has to be constant-initialized with a string literal, or with another string enum member.

While string enums don’t have auto-incrementing behavior, string enums have the benefit that they “serialize” well. In other words, if you were debugging and had to read the runtime value of a numeric enum, the value is often opaque — it doesn’t convey any useful meaning on its own (though reverse mapping can often help). String enums allow you to give a meaningful and readable value when your code runs, independent of the name of the enum member itself.

Technically enums can be mixed with string and numeric members, but it’s not clear why you would ever want to do so:

Unless you’re really trying to take advantage of JavaScript’s runtime behavior in a clever way, it’s advised that you don’t do this.

Computed and constant members

Each enum member has a value associated with it which can be either constant or computed. An enum member is considered constant if:

    It is the first member in the enum and it has no initializer, in which case it’s assigned the value 0 :

  1. a literal enum expression (basically a string literal or a numeric literal)
  2. a reference to previously defined constant enum member (which can originate from a different enum)
  3. a parenthesized constant enum expression
  4. one of the + , — , ~ unary operators applied to constant enum expression
  5. + , — , * , / , % , > , >>> , & , | , ^ binary operators with constant enum expressions as operands
Читайте также:  Html textarea no scrolls

It is a compile time error for constant enum expressions to be evaluated to NaN or Infinity .

In all other cases enum member is considered computed.

Union enums and enum member types

There is a special subset of constant enum members that aren’t calculated: literal enum members. A literal enum member is a constant enum member with no initialized value, or with values that are initialized to

  • any string literal (e.g. «foo» , «bar» , «baz» )
  • any numeric literal (e.g. 1 , 100 )
  • a unary minus applied to any numeric literal (e.g. -1 , -100 )

When all members in an enum have literal enum values, some special semantics come into play.

The first is that enum members also become types as well! For example, we can say that certain members can only have the value of an enum member:

The other change is that enum types themselves effectively become a union of each enum member. With union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in the enum itself. Because of that, TypeScript can catch bugs where we might be comparing values incorrectly. For example:

In that example, we first checked whether x was not E.Foo . If that check succeeds, then our || will short-circuit, and the body of the ‘if’ will run. However, if the check didn’t succeed, then x can only be E.Bar , so it doesn’t make sense to see whether it’s equal to E.Bar .

Enums are real objects that exist at runtime. For example, the following enum

can actually be passed around to functions

Even though Enums are real objects that exist at runtime, the keyof keyword works differently than you might expect for typical objects. Instead, use keyof typeof to get a Type that represents all Enum keys as strings.

In addition to creating an object with property names for members, numeric enums members also get a reverse mapping from enum values to enum names. For example, in this example:

TypeScript compiles this down to the following JavaScript:

In this generated code, an enum is compiled into an object that stores both forward ( name -> value ) and reverse ( value -> name ) mappings. References to other enum members are always emitted as property accesses and never inlined.

Keep in mind that string enum members do not get a reverse mapping generated at all.

In most cases, enums are a perfectly valid solution. However sometimes requirements are tighter. To avoid paying the cost of extra generated code and additional indirection when accessing enum values, it’s possible to use const enums. Const enums are defined using the const modifier on our enums:

Const enums can only use constant enum expressions and unlike regular enums they are completely removed during compilation. Const enum members are inlined at use sites. This is possible since const enums cannot have computed members.

Читайте также:  Java set display mode

in generated code will become

Inlining enum values is straightforward at first, but comes with subtle implications. These pitfalls pertain to ambient const enums only (basically const enums in .d.ts files) and sharing them between projects, but if you are publishing or consuming .d.ts files, these pitfalls likely apply to you, because tsc —declaration transforms .ts files into .d.ts files.

  1. For the reasons laid out in the isolatedModules documentation, that mode is fundamentally incompatible with ambient const enums. This means if you publish ambient const enums, downstream consumers will not be able to use isolatedModules and those enum values at the same time.
  2. You can easily inline values from version A of a dependency at compile time, and import version B at runtime. Version A and B’s enums can have different values, if you are not very careful, resulting in surprising bugs, like taking the wrong branches of if statements. These bugs are especially pernicious because it is common to run automated tests at roughly the same time as projects are built, with the same dependency versions, which misses these bugs completely.
  3. importsNotUsedAsValues: «preserve» will not elide imports for const enums used as values, but ambient const enums do not guarantee that runtime .js files exist. The unresolvable imports cause errors at runtime. The usual way to unambiguously elide imports, type-only imports, does not allow const enum values, currently.

Here are two approaches to avoiding these pitfalls:

A. Do not use const enums at all. You can easily ban const enums with the help of a linter. Obviously this avoids any issues with const enums, but prevents your project from inlining its own enums. Unlike inlining enums from other projects, inlining a project’s own enums is not problematic and has performance implications. B. Do not publish ambient const enums, by deconstifying them with the help of preserveConstEnums . This is the approach taken internally by the TypeScript project itself. preserveConstEnums emits the same JavaScript for const enums as plain enums. You can then safely strip the const modifier from .d.ts files in a build step.

This way downstream consumers will not inline enums from your project, avoiding the pitfalls above, but a project can still inline its own enums, unlike banning const enums entirely.

Ambient enums are used to describe the shape of already existing enum types.

One important difference between ambient and non-ambient enums is that, in regular enums, members that don’t have an initializer will be considered constant if its preceding enum member is considered constant. By contrast, an ambient (and non-const) enum member that does not have an initializer is always considered computed.

In modern TypeScript, you may not need an enum when an object with as const could suffice:

The biggest argument in favour of this format over TypeScript’s enum is that it keeps your codebase aligned with the state of JavaScript, and when/if enums are added to JavaScript then you can move to the additional syntax.

Читайте также:  Simple Example of CSS adjacent sibling selectors

The TypeScript docs are an open source project. Help us improve these pages by sending a Pull Request ❤

Источник

Typescript перечисление возможных значений

Тип enum или перечисление позволяет определить набор именнованных констант, которые описывают определенные состояния.

Для определения перечисления применяется ключевое слово enum . Например, объявим следующее перечисление:

Перечисление называется Season и имеет четыре элемента. Теперь используем перечисление:

enum Season < Winter, Spring, Summer, Autumn >; let current: Season = Season.Summer; console.log(current); // 2 current = Season.Autumn; // изменение значения

Здесь создается переменная current , которая имеет тип Season. При этом консоль браузера выведет нам число 2 — значение константы Season.Summer .

Числовые перечисления

По умолчанию константы перечисления, как в примере выше, представляют числовые значения. То есть это так называемое числовое перечисление, в котором каждой константе сопоставляется числовое значение.

Так, созданное выше в примере перечисление

фактически эквивалентно следующему:

Хотя мы можем явным образом переопределить эти значения. Так, мы можем задать значение одной константы, тогда значения следующих констант будет увеличиваться на единицу:

Либо можно каждой константе задать свое значение:

Также мы можем получить непосредственно текстовое значение:

enum Season < Winter=0, Spring=1, Summer=2, Autumn=3 >; var current: string = Season[2]; // 2 - числовое значение Summer console.log(current); // Summer

Строковые перечисления

Кроме числовых перечислений в TypeScript есть строковые перечисления, константы которых принимают строковые значения:

enum Season < Winter = "Зима", Spring = "Весна", Summer = "Лето", Autumn = "Осень" >; var current: Season = Season.Summer; console.log(current); // Лето

Смешанные гетерогенные перечисления

Также можно определять смешанные перечисления, константы которых могут числа и строки.

enum Season < Winter = 1, Spring = "Весна", Summer = 3, Autumn = "Осень" >; var current: Season = Season.Summer; console.log(current); // 3 console.log(Season.Autumn); // Осень

Перечисления в функциях

Перечисление может выступать в качестве параметра функции.

enum DayTime < Morning, Evening >; function welcome(dayTime: DayTime) < if(dayTime === DayTime.Morning)< console.log("Доброе утро"); >else < console.log("Добрый вечер"); >> let current: DayTime = DayTime.Morning; welcome(current); // Доброе утро welcome(DayTime.Evening); // Добрый вечер

Каждая константа перечисления описывает некоторое состояние. И функция welcome() в виде параметра dayTime принимает это состояние и в зависимости от полученного значения выводит на консоль определенное значение.

Однако стоит отметить, что поскольку здесь перечисление DayTime представляет числовое перечисление, то в реальности в функцию welcome() мы можем передать числовые значения:

Либо даже определить параметр функции как числовой и передавать константы числового перечисления:

enum DayTime < Morning, Evening >; function welcome(dayTime: number) < if(dayTime === DayTime.Morning)< console.log("Доброе утро"); >else < console.log("Добрый вечер"); >> let current: DayTime = DayTime.Morning; welcome(current); // Доброе утро welcome(DayTime.Evening); // Добрый вечер

Пример параметра-строкового перечисления:

enum DayTimeMessage < Morning = "Доброе утро", Evening = "Добрый вечер" >; function welcome(message: DayTimeMessage) < console.log(message); >let mes: DayTimeMessage = DayTimeMessage.Morning; welcome(mes); // Доброе утро welcome(DayTimeMessage.Evening); // Добрый вечер

При использовании строковых перечислений в отличие от числовых мы не можем передать переметру произвольную строку:

В этом случе компилятор выкатит нам ошибку при компиляции.

В то же время если параметр представляет тип string , то такому параметру можно передавать как строки, так и константы строкового перечисления:

enum DayTimeMessage < Morning = "Доброе утро", Evening = "Добрый вечер" >; function welcome(message: string) < console.log(message); >let mes: DayTimeMessage = DayTimeMessage.Morning; welcome(mes); // Доброе утро welcome(DayTimeMessage.Evening); // Добрый вечер

Источник

Оцените статью