Подробно о перечислениях в Typescript

В компьютерном программировании перечисляемый тип представляет собой тип данных, который состоит из набора именованных значений, называемых элементами, членами, перечислимыми коллекциями или перечислителями типа. В TypeScript он использует ключевое слово enum, которое определяет набор именованных констант, организованных в коллекцию. 

Для удобства рассмотрим перечисления в рабочей среде React. Примеры не ограничиваются данной библиотекой и могут быть задействованы в других рабочих средах TypeScript. 

TypeScript и Create React App

Create React App на основе JavaScript  —  отличный инструмент для изучения разных функциональностей и библиотек. В этот раз мы применим его с TypeScript, и тому есть объяснение. 

JavaScript не поддерживает перечисления, хотя enum является в нем зарезервированным словом. Точнее говоря, он предусматривает использование этого типа данных в будущем. 

Перечисления TypeScript предоставляют структурированные обертки для строк или чисел. 

Устанавливаем Create React App

npx create-react-app react-enum --template typescript
cd react-enum

Выполняем npm start и запускаем Create React App на основе TypeScript. 

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

Перечисление определяется ключевым словом enum, за которым следуют его имя и элементы, являющиеся именами констант в скобках. 

enum Name {Constant1, Constant2, ...};

Имена перечислений и констант традиционно указываются в стиле PascalСase, в соответствии с которым каждое составное слово начинается с заглавной буквы.

Ниже представлен пример перечисления Fruit, который включает 4 фрукта: 

enum Fruit {
Apple,
Orange,
Peach,
Pear,
}

По умолчанию перечисления являются числовыми, т.е. Fruit.Apple эквивалентен 0, Fruit.Orange —  1, Fruit.Peach —  2 и Fruit.Pear —  3. 

Число также может задаваться в произвольно: 

enum Fruit {
Apple = 5,
Orange = -1,
Peach,
Pear,
}

В этом примере Fruit.Apple равняется 5, Fruit.Orange —  -1, Fruit.Peach —  0 и Fruit.Pear —  1.

Для удобочитаемости перечисления можно определять как строки. Строковым перечислениям устанавливаются строковые значения. 

enum Color {
Green = 'Green',
LightGreen = 'Light Green',
White = 'White',
}

В вышеуказанном примере Color.Green эквивалентен 'Green', Color.LightGreen —  'Light Green' и Color.White —  'White'.

Существуют также гетерогенные перечисления, содержащие как числовые, так и строковые значения. 

enum Task {
Name = 'Todo List',
Assigned = 0,
InProgress,
Finished
}

С учетом данного определения Task.Name принимает значение 'Todo List', Task.Assigned —  0, Task.InProgress —  1 и Task.Finished —  2.

Преобразуем src/App.tsx посредством следующего кода: 

enum Fruit {
Apple,
Orange,
Peach,
Pear,
}

enum Color {
Green = 'Green',
LightGreen = 'Light Green',
White = 'White',
}

enum Task {
Name = 'Todo List',
Assigned = 0,
InProgress,
Finished
}

function App() {
return (
<ul>
<li>{JSON.stringify(Fruit)}</li>
<li>{Fruit.Apple}</li>
<li>{Fruit.Orange}</li>
<li>{Fruit.Peach}</li>
<li>{Fruit.Pear}</li>
<li>{JSON.stringify(Color)}</li>
<li>{Color.Green}</li>
<li>{Color.LightGreen}</li>
<li>{Color.White}</li>
<li>{JSON.stringify(Task)}</li>
<li>{Task.Name}</li>
<li>{Task.Assigned}</li>
<li>{Task.InProgress}</li>
<li>{Task.Finished}</li>
</ul>
);
}

export default App;

Строки 1–6 определяют перечисление Fruit, которое отображается в строках 24–28. Перечисление является объектом (строка 24) с ключами имен констант и числовыми значениями  —  {"0":"Apple","1":"Orange","2":"Peach","3":"Pear","Apple":0,"Orange":1,"Peach":2,"Pear":3}. Следовательно, мы можем использовать сопоставление объектов (Fruit.Apple или Fruit.['Apple']) и обратное сопоставление (Fruit[0]). 

Строки 8–12 определяют перечисление Color, отображаемое в строках 29–32. Это объект {"Green":"Green","LightGreen":"Light Green","White":"White"}. Как видно, элементы строковых перечислений не поддерживают обратное сопоставление. 

Строки 14–19 определяют перечисление Task, отображаемое в строках 33–37. Это объект {"0":"Assigned","1":"InProgress","2":"Finished","Name":"Todo List","Assigned":0,"InProgress":1,"Finished":2}. Он относится к гетерогенным перечислениям, и только числовые элементы допускают обратное сопоставление. 

Запускаем приложение, и в браузере выводится следующий результат: 

∙ {"0":"Apple","1":"Orange","2":"Peach","3":"Pear","Apple":0,"Orange":1,"Peach":2,"Pear":3}
∙ 0
∙ 1
∙ 2
∙ 3
∙ {"Green":"Green","LightGreen":"Light Green","White":"White"}
∙ Green
∙ LightGreen
∙ White
∙ {"0":"Assigned","1":"InProgress","2":"Finished","Name":"Todo List","Assigned":0,"InProgress":1,"Finished":2}
∙ Todo List
∙ 0
∙ 1
∙ 2

Ни один браузер напрямую не поддерживает TypeScript. Для выполнения в них код преобразуется в JavaScript (точнее в ES5). Перечисления конвертируются в нижеуказанный код JavaScript: 

var Fruit;

(function (Fruit) {
Fruit[Fruit["Apple"] = 0] = "Apple";
Fruit[Fruit["Orange"] = 1] = "Orange";
Fruit[Fruit["Peach"] = 2] = "Peach";
Fruit[Fruit["Pear"] = 3] = "Pear";
})(Fruit || (Fruit = {}));

var Color;

(function (Color) {
Color["Green"] = "Green";
Color["LightGreen"] = "Light Green";
Color["White"] = "White";
})(Color || (Color = {}));

var Task;

(function (Task) {
Task["Name"] = "Todo List";
Task[Task["Assigned"] = 0] = "Assigned";
Task[Task["InProgress"] = 1] = "InProgress";
Task[Task["Finished"] = 2] = "Finished";
})(Task || (Task = {}));

Константные выражения перечисления 

Элементы перечислений инициализируются константными выражениями enum, которые полностью вычисляются во время компиляции. Перечислим допустимые выражения: 

  • литеральное выражение перечисления числового значения или строки: 
enum BooleanValue {
  Yes = 1,
  No = 0,
}
enum Result {
  Right = 'Right',
  Wrong = 'Wrong',
}
  • значение, получаемое в результате действия бинарных операторов +, -, *, /, %, <<, >>, >>>, &, |, ^ с константными выражениями перечисления в качестве операндов: 
enum AccessCode {
None = 1 << 0,
Read = 1 << 1,
Write = 1 << 2,
}
  • ссылка на ранее определенный константный элемент перечисления: 
enum AccessCode {
  None = 1 << 0,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadOrWrite = Read | Write,
}
enum Permission {
  NotAllowed = 0,
  Allowed = AccessCode.ReadOrWrite,
}
  • константное выражение перечисления в круглых скобках:  
enum Values {
NumberValue = (2 * 3),
StringValue = ('Today' + 'Tomorrow'),
}
  • один из унарных операторов +, -, ~, применяемый к константному выражению перечисления: 
enum ThreeOps {
Plus = +1,
Minus = -1,
Tilde = ~1,
}
  • вызов функции: 
const add3 = (value: number) => value + 3;
enum ComputedValues {
V1 = add3(5),
V2 = add3(7),
}

Поместим все эти перечисления в src/App.tsx и посмотрим, как они работают: 

enum BooleanValue {
Yes = 1,
No = 0,
}

enum Result {
Right = 'Right',
Wrong = 'Wrong',
}

enum AccessCode {
None = 1 << 0,
Read = 1 << 1,
Write = 1 << 2,
ReadOrWrite = Read | Write,
}

enum Permission {
NotAllowed = 0,
Allowed = AccessCode.ReadOrWrite,
}

enum Values {
NumberValue = (2 * 3),
StringValue = ('Today' + 'Tomorrow'),
}

enum ThreeOps {
Plus = +1,
Minus = -1,
Tilde = ~1,
}

const add3 = (value: number) => value + 3;
enum ComputedValues {
V1 = add3(5),
V2 = add3(7),
}

function App() {
return (
<ul>
<li>{JSON.stringify(BooleanValue)}</li>
<li>{BooleanValue.Yes}</li>
<li>{BooleanValue.No}</li>
<li>{JSON.stringify(Result)}</li>
<li>{Result.Right}</li>
<li>{Result.Wrong}</li>
<li>{JSON.stringify(AccessCode)}</li>
<li>{AccessCode.None}</li>
<li>{AccessCode.Read}</li>
<li>{AccessCode.Write}</li>
<li>{AccessCode.ReadOrWrite}</li>
<li>{JSON.stringify(Permission)}</li>
<li>{Permission.NotAllowed}</li>
<li>{Permission.Allowed}</li>
<li>{JSON.stringify(Values)}</li>
<li>{Values.NumberValue}</li>
<li>{Values.StringValue}</li>
<li>{JSON.stringify(ThreeOps)}</li>
<li>{ThreeOps.Plus}</li>
<li>{ThreeOps.Minus}</li>
<li>{ThreeOps.Tilde}</li>
<li>{JSON.stringify(ComputedValues)}</li>
<li>{ComputedValues.V1}</li>
<li>{ComputedValues.V2}</li>
</ul>
);
}

export default App;
  • Строки 1–4 определяют перечисление BooleanValue, отображаемое в строках 43–45.
  • Строки 6–9 определяют перечисление Result, отображаемое в строках 46–48.
  • Строки 11–16 определяют перечисление AccessCode, отображаемое в строках 49–53.
  • Строки 18–21 определяют перечисление Permission, отображаемое в строках 54–56.
  • Строки 23–26 определяют перечисление Values, отображаемое в строках 57–59.
  • Строки 28–32 определяют перечисление ThreeOps, отображаемое в строках 60–63.
  • Строка 34 определяет выражение функции add3.
  • Строки 35–38 определяют перечисление ComputedValues, отображаемое в строках 64–66. 

Запускаем приложение, и в браузере появляется следующий результат: 

∙ {"0":"No","1":"Yes","Yes":1,"No":0}
∙ 1
∙ 0
∙ {"Right":"Right","Wrong":"Wrong"}
∙ Right
∙ Wrong
∙ {"1":"None","2":"Read","4":"Write","6":"ReadOrWrite","None":1,"Read":2,"Write":4,"ReadOrWrite":6}
∙ 1
∙ 2
∙ 4
∙ 6
∙ {"0":"NotAllowed","6":"Allowed","NotAllowed":0,"Allowed":6}
∙ 0
∙ 6
∙ {"6":"NumberValue","NumberValue":6,"StringValue":"TodayTomorrow"}
∙ 6
∙ TodayTomorrow
∙ {"1":"Plus","Plus":1,"Minus":-1,"-1":"Minus","Tilde":-2,"-2":"Tilde"}
∙ 1
∙ -1
∙ -2
∙ {"8":"V1","10":"V2","V1":8,"V2":10}
∙ 8
∙ 10

Перечисления преобразуются в данный код JavaScript: 

var BooleanValue;

(function (BooleanValue) {
BooleanValue[BooleanValue["Yes"] = 1] = "Yes";
BooleanValue[BooleanValue["No"] = 0] = "No";
})(BooleanValue || (BooleanValue = {}));

var Result;

(function (Result) {
Result["Right"] = "Right";
Result["Wrong"] = "Wrong";
})(Result || (Result = {}));

var AccessCode;

(function (AccessCode) {
AccessCode[AccessCode["None"] = 1] = "None";
AccessCode[AccessCode["Read"] = 2] = "Read";
AccessCode[AccessCode["Write"] = 4] = "Write";
AccessCode[AccessCode["ReadOrWrite"] = 6] = "ReadOrWrite";
})(AccessCode || (AccessCode = {}));

var Permission;

(function (Permission) {
Permission[Permission["NotAllowed"] = 0] = "NotAllowed";
Permission[Permission["Allowed"] = AccessCode.ReadOrWrite] = "Allowed";
})(Permission || (Permission = {}));

var Values;

(function (Values) {
Values[Values["NumberValue"] = 6] = "NumberValue";
Values["StringValue"] = "TodayTomorrow";
})(Values || (Values = {}));

var ThreeOps;

(function (ThreeOps) {
ThreeOps[ThreeOps["Plus"] = 1] = "Plus";
ThreeOps[ThreeOps["Minus"] = -1] = "Minus";
ThreeOps[ThreeOps["Tilde"] = -2] = "Tilde";
})(ThreeOps || (ThreeOps = {}));

const add3 = value => value + 3;

var ComputedValues;

(function (ComputedValues) {
ComputedValues[ComputedValues["V1"] = add3(5)] = "V1";
ComputedValues[ComputedValues["V2"] = add3(7)] = "V2";
})(ComputedValues || (ComputedValues = {}));

Типы перечислений

В TypeScript определяемое перечисление является типом. Помимо этого, типом может быть и элемент перечисления. 

Рассмотрим src/App.tsx

enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
const firstDay: Day = Day.Sunday;
const secondDay: Day = Day.Monday;
const sixthDay: Day = 5;
const seventhDay: Day = 6;
const isWeekend: (day: Day) => string = (day: Day) => [Day.Saturday, Day.Sunday].includes(day) ? 'Yes' : 'No';
const myValue: Day.Sunday = 10;

// const x: firstDay = 5;
// const y: myValue = 5;

function App() {
return (
<ul>
<li>{typeof Day}</li>
<li>{typeof Day.Sunday}</li>
<li>{typeof firstDay}</li>
<li>{typeof secondDay}</li>
<li>{typeof sixthDay}</li>
<li>{typeof seventhDay}</li>
<li>{isWeekend(firstDay)}</li>
<li>{isWeekend(secondDay)}</li>
<li>{isWeekend(sixthDay)}</li>
<li>{isWeekend(seventhDay)}</li>
<li>{typeof myValue}</li>
<li>{myValue}</li>
</ul>
);
}

export default App;

Строки 1–9 определяют перечисление Day. В строке 23 тип выводится как object.

Day задействуется как тип для определения переменных const в строках 10–13. Эти переменные на самом деле имеют типы чисел (строки 25–28). 

В строке 14 Day применяется как тип для определения выражения функции, которая вызывается в строках 29–32.

Строка 15 задействует элемент перечисления Day.Sunday для определения типа number (строка 33); фактическое значение (строка 34) выходит за пределы значений перечисления. 

Для сравнения, строки 16 и 17 не могут использовать определяемую переменную для определения типов. 

Запускаем приложение и получаем результат в браузере: 

∙ object
∙ number
∙ number
∙ number
∙ number
∙ number
∙ Yes
∙ No
∙ No
∙ Yes
∙ number
∙ 10

Перечисления ведут себя аналогично объектам. Элемент перечисления можно обозначить посредством точечной или скобочной нотации. Например, Day.Sunday и Day['Sunday'] взаимозаменяемы. 

Запускаем src/App.tsx

enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}

function App() {
return (
<ul>
<li>{Object.keys(Day).map((key: string) => Day[key as keyof typeof Day])}</li>
</ul>
);
}

export default App;

В строке 14 отображается ошибка компиляции: Element implicitly has an 'any' type because index expression is not of type 'number'.ts (элемент неявно имеет тип ‘any’, поскольку индексное выражение не относится к типу ‘number’). 

При наличии скобочной нотации индексное выражение становится допустимым элементом перечисления. Такой вариант сработает как в случае с Day[key as 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday'], так и Day[key as 'Sunday']

Однако лучше воспользоваться ключевым словом keyof. keyof typeof получает тип, который представляет все ключи перечислений в виде строк, а именно 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday'.

Перед вами код, который проходит компиляцию: 

enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}

function App() {
return (
<ul>
<li>{Object.keys(Day).map((key: string) => Day[key as keyof typeof Day])}</li>
</ul>
);
}

export default App;

Выполняем его и в браузере видим значения сопоставления объектов и обратного сопоставления: 

∙ SundayMondayTuesdayWednesdayThursdayFridaySaturday0123456

Объекты перечислений 

Во время выполнения перечисления являются объектами. Их можно передавать в функции. 

Ниже представлен измененный src/App.tsx:

enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}

const getWednesdayId: (day: {Wednesday: number}) => number = (day: {Wednesday: number}) => day.Wednesday;

function App() {
return (
<ul>
<li>{getWednesdayId(Day)}</li>
</ul>
);
}

export default App;

Строка 11  —  это выражение функции, которое принимает объект в качестве параметра. Оно не связано с перечислением Day, за исключением того, что у обоих есть ключ Wednesday.

Вызываем функцию (строка 16) с Day, что приводит к следующему результату в браузере: 

∙ 3

Перечисления  —  это объекты, но сгенерированный код несколько сложноват. Day преобразуется в представленный ниже код JavaScript. 

(function (Day) {
Day[Day["Sunday"] = 0] = "Sunday";
Day[Day["Monday"] = 1] = "Monday";
Day[Day["Tuesday"] = 2] = "Tuesday";
Day[Day["Wednesday"] = 3] = "Wednesday";
Day[Day["Thursday"] = 4] = "Thursday";
Day[Day["Friday"] = 5] = "Friday";
Day[Day["Saturday"] = 6] = "Saturday";
})(Day || (Day = {}));

Начиная с TypeScript 3.4, в обиход введены утверждения const. Их синтаксис представляет собой утверждение типа с суффиксом as const, например 123 as const. Создание литеральных выражений с утверждениями const означает, что: 

  • литеральные типы в выражении нельзя расширять;
  • литералы объектов получают свойства readonly
  • литералы массивов становятся кортежами readonly

Мы можем создать объект с утверждением const в src/App.tsx:

const Day = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6,
} as const;

function App() {
return (
<ul>
<li>{Object.keys(Day).map((key: string) => Day[key as keyof typeof Day])}</li>
</ul>
);
}

export default App;

Он не поддерживает обратное сопоставление. При выполнении получаем в браузере значения сопоставления объектов: 

∙ 0123456

Ниже представлен сгенерированный код JavaScript:

const Day = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6
};

Это не перечисление, но может применяться аналогичным ему образом. Сгенерированный код прост и похож на первоначальный код TypeScript. Следовательно, утверждение const подойдет в качестве полноценной альтернативы перечислениям TypeScript. 

Заключение 

В статье были рассмотрены перечисления TypeScript в рабочей среде React. Подведем краткие итоги.

  • Выделяют три вида перечислений: числовые, строковые и смешенные гетерогенные. 
  • Элементы перечислений задействуют различные константные выражения. 
  • И перечисления, и их элементы могут использоваться как типы. 
  • Перечисления  —  это объекты во время выполнения. Их можно передавать в функции. 
  • Утверждения const —  подходящая альтернатива перечислениям TypeScript. 

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Jennifer Fu: A Complete Guide to Enums in TypeScript

Предыдущая статьяОператоры PHP: If, Else и If-Else
Следующая статья5 методов написания чистого кода для любого проекта