В компьютерном программировании перечисляемый тип представляет собой тип данных, который состоит из набора именованных значений, называемых элементами, членами, перечислимыми коллекциями или перечислителями типа. В 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.
Читайте также:
Читайте нас в Telegram, VK и Дзен
Перевод статьи Jennifer Fu: A Complete Guide to Enums in TypeScript