Примитивный тип Enum¶
При создании приложений тяжело обойтись без большого количества специальных конфигурационных значений. Подобные значения разработчики выносят в отдельные классы со статическими свойствами или модули с константами, избавляя таким образом свой код от магических значений.
TypeScript привносит новую синтаксическую конструкцию, называемую Enum (перечисление). enum представляет собой набор логически связанных констант, в качестве значений которых могут выступать как числа, так и строки.
Enum примитивный перечисляемый тип¶
Enum — это конструкция, состоящая из набора именованных констант, именуемая списком перечисления и определяемая такими примитивными типами, как number и string . Enum объявляется с помощью ключевого слова enum .
Перечисления с числовым значением¶
Идентификаторы-имена для перечислений enum принято задавать во множественном числе. В случае, когда идентификаторам констант значение не устанавливается явно, они ассоциируются с числовым значениями, в порядке возрастания, начиная с нуля.
1 2 3 4 5
enum Fruits Apple, // 0 Pear, // 1 Banana, // 2 >
Также можно установить любое значение вручную.
1 2 3 4 5
enum Citrus Lemon = 2, // 2 Orange = 4, // 4 Lime = 6, // 6 >
Если указать значение частично, то компилятор будет стараться соблюдать последовательность.
1 2 3 4 5 6 7
enum Berrys Strawberry = 1, Raspberry, // 2 Blueberry = 4, Cowberry, // 5 >
Компилятор рассчитывает значение автоматически только на основе значения предыдущего члена перечисления. То есть, если первой и третьей константе было установленно значение 10 и 20 .
1 2 3 4 5 6
enum Keys A = 10, B, // 11 C = 20, D, // 21 >
Поскольку enum позволяет разработчику задавать одинаковые значения своим константам, при частично устанавливаемых значениях нужно быть предельно внимательным чтобы не допустить ещё и повторений со стороны самого enum .
1 2 3 4 5 6
enum Keys A = 10, B, // 11 C = 10, D, // 11 >
Вдобавок ко всему enum позволяет задавать псевдонимы (alias). Псевдонимам устанавливается значение константы, на которую они ссылаются.
1 2 3 4 5
enum Langues Apple, // en, value = 0 Apfel = Apple, // de, value = 0 LaPomme = Apple, // fr, value = 0 >
При обращении к константе перечисления через точечную нотацию, будет возвращено значение. А при обращении к перечислению с помощью скобочной нотации и указания значения в качестве ключа, будет возвращено строковое представление идентификатора константы.
let value: number = Fruits.Apple; // 0 let identificator: string = Fruits[value]; // “Apple”
Поскольку enum представляет реальные значения, без которых программа будет неработоспособна, он обязан оставаться в коде после компиляции. Поэтому чтобы быстрее понять enum , нужно посмотреть на него в скомпилированном конечном виде. Но прежде создадим его самостоятельно.
1 шаг. Тем, кто ранее работал с enum , уже известно, что он позволяет получать строковое представление константы, а также значение, ассоциированное с ней. Поэтому для его создания требуется ассоциативный массив, коими в JavaScript являются объекты. Назовем объект Fruits и передадим его в качестве аргумента в функцию initialization , которая будет содержать код его инициализации.
1 2 3
let Fruits = <>; function initialization(Fruits) <>
2 шаг. Создадим поле с именем Apple и присвоим ему в качестве значения число 0 .
1 2 3 4 5
let Fruits = <>; function initialization(Fruits) Fruits['Apple'] = 0; >
3 шаг. Ассоциация константа-значение произведена, осталось создать зеркальную ассоциацию значение-константа. Для этого создадим ещё одно поле, у которого в качестве ключа будет выступать значение 0 , а в качестве значения — строковое представление константы, то есть имя.
1 2 3 4 5 6
let Fruits = <>; function initialization(Fruits) Fruits['Apple'] = 0; Fruits[0] = 'Apple'; >
4 шаг. Теперь сократим код, но сначала вспомним, что результатом операции присваивания является значение правого операнда. Поэтому сохраним результат первого выражения в переменную value , а затем используем её в качестве ключа во втором выражении.
1 2 3 4 5 6
let Fruits = <>; function initialization(Fruits) let value = (Fruits['Apple'] = 0); // то же самое что value = 0 Fruits[value] = 'Apple'; // то же самое что Fruits[0] = "Apple"; >
5 шаг. Продолжим сокращать и в первом выражении откажемся от переменной value , а во втором выражении на её место поместим первое выражение.
1 2 3 4 5
let Fruits = <>; function initialization(Fruits) Fruits[(Fruits['Apple'] = 0)] = 'Apple'; >
6 шаг. Теперь проделаем то же самое для двух других констант.
1 2 3 4 5 6 7
let Fruits = <>; function initialization(Fruits) Fruits[(Fruits['Apple'] = 0)] = 'Apple'; Fruits[(Fruits['Lemon'] = 1)] = 'Lemon'; Fruits[(Fruits['Orange'] = 2)] = 'Orange'; >
7 шаг. Теперь превратим функцию intialization в самовызывающееся функциональное выражение и лучше анонимное.
1 2 3 4 5 6 7
let Fruits = <>; (function (Fruits) Fruits[(Fruits['Apple'] = 0)] = 'Apple'; Fruits[(Fruits['Pear'] = 1)] = 'Pear'; Fruits[(Fruits['Banana'] = 2)] = 'Banana'; >)(Fruits);
8 шаг. И перенесем инициализацию объекта прямо на место вызова.
1 2 3 4 5 6
let Fruits; (function (Fruits) Fruits[(Fruits['Apple'] = 0)] = 'Apple'; Fruits[(Fruits['Pear'] = 1)] = 'Pear'; Fruits[(Fruits['Banana'] = 2)] = 'Banana'; >)(Fruits || (Fruits = <>));
Перечисление готово. Осталось сравнить созданное перечисление с кодом, полученным в результате компиляции.
1 2 3 4 5 6 7
// enum сгенерированный typescript compiler let Fruits; (function (Fruits) Fruits[(Fruits['Apple'] = 0)] = 'Apple'; Fruits[(Fruits['Pear'] = 1)] = 'Pear'; Fruits[(Fruits['Banana'] = 2)] = 'Banana'; >)(Fruits || (Fruits = <>));
Теперь добавим в рассматриваемое перечисление псевдоним LaPomme (яблоко на французском языке) для константы Apple .
1 2 3 4 5 6 7
enum Fruits Apple, // 0 Pear, // 1 Banana, // 2 LaPomme = Apple, // 0 >
И снова взглянем на получившийся в результате компиляции код. Можно увидеть, что псевдоним создается так же, как обычная константа, но в качестве значения ему присваивается значение, идентичное константе, на которую он ссылается.
1 2 3 4 5 6
(function (Fruits) Fruits[(Fruits['Apple'] = 0)] = 'Apple'; Fruits[(Fruits['Lemon'] = 1)] = 'Lemon'; Fruits[(Fruits['Ornge'] = 2)] = 'Ornge'; Fruits[(Fruits['LaPomme'] = 0)] = 'LaPomme'; // псевдоним >)(Fruits || (Fruits = <>));
Перечисления со строковым значением¶
Помимо значения, принадлежащего к типу number , TypeScript позволяет указывать значения с типом string .
1 2 3 4 5
enum FruitColors Red = '#ff0000', Green = '#00ff00', Blue = '#0000ff', >
Но в случае, когда константам присваиваются строки, ассоциируется только ключ со значением. Обратная ассоциация (значение-ключ) — отсутствует. Простыми словами, по идентификатору (имени константы) можно получить строковое значение, но по строковому значению получить идентификатор (имя константы) невозможно.
1 2 3 4 5 6
var FruitColors; (function (FruitColors) FruitColors['Red'] = '#ff0000'; FruitColors['Green'] = '#00ff00'; FruitColors['Blue'] = '#0000ff'; >)(FruitColors || (FruitColors = <>));
тем не менее остается возможность создавать псевдонимы (alias).
1 2 3 4 5 6 7 8 9
enum FruitColors Red = '#ff0000', Green = '#00ff00', Blue = '#0000ff', Rouge = Red, // fr "#ff0000" Vert = Green, // fr "#00ff00" Bleu = Blue, // fr "#0000ff" >
И снова изучим скомпилированный код. Можно убедиться, что псевдонимы создаются так же, как и константы. А значение, присваиваемое псевдонимам, идентично значению констант, на которые они ссылаются.
1 2 3 4 5 6 7 8 9
var FruitColors; (function (FruitColors) FruitColors['Red'] = '#ff0000'; FruitColors['Green'] = '#00ff00'; FruitColors['Blue'] = '#0000ff'; FruitColors['Rouge'] = '#ff0000'; FruitColors['Vert'] = '#00ff00'; FruitColors['Bleu'] = '#0000ff'; >)(FruitColors || (FruitColors = <>));
Смешанное перечисление¶
Если в одном перечислении объявлены числовые и строковые константы, то такое перечисление называется смешанным (mixed enum).
Со смешанным перечислением связаны две неочевидные особенности.
Первая из них заключается в том, что константам, которым значение не задано явно, присваивается числовое значение по правилам перечисления с числовыми константами.
1 2 3 4
enum Stones Peach, // 0 Apricot = 'apricot', >
Вторая особенность заключается в том, что если константа, которой значение не было присвоено явно, следует после константы со строковым значением, то такой код не скомпилируется. Причина заключается в том, что как было рассказано в секции Перечисления с числовым значением, если константе значение не было установлено явно, то её значение будет рассчитано как значение предшествующей ей константе +1 , либо 0 , в случае её отсутствия. А так как у предшествующей константы значение принадлежит к строковому типу, то рассчитать число на его основе не представляется возможным.
1 2 3 4 5 6
enum Stones Peach, // 0 Apricot = 'apricot', Cherry, // Error Plum, // Error >
Для разрешения этой проблемы в смешанном перечислении, константе, которая была объявлена после константы со строковым значением, необходимо задавать значение явно.
1 2 3 4 5 6
enum Stones Peach, // 0 Apricot = 'apricot', Cherry = 1, // 1 Plum, // 2 >
Перечисление в качестве типа данных¶
Может возникнуть мысль использовать перечисление в качестве типа данных переменной или параметра. Это вполне нормальное желание, но нужно быть очень осторожным: в TypeScript с перечислением связан один достаточно неприятный нюанс. Дело в том, что пока в перечислении есть хотя бы одна константа с числовым значением, он будет совместим с типом number . Простыми словами, любое число проходит проверку совместимости типов с любым перечислением.
Функцию, тип параметра которой является смешанным перечислением, благополучно получится вызвать как с константой перечисления в качестве аргумента, так и с любым числом. Вызвать эту же функцию с идентичной константе перечисления строкой уже не получится.
1 2 3 4 5 6 7 8 9 10 11 12 13
enum Fruits Apple, Pear, Banana = 'banana', > function isFruitInStore(fruit: Fruits): boolean return true; > isFruitInStore(Fruits.Banana); // ок isFruitInStore(123456); // ок isFruitInStore('banana'); // Error
Если перечисление содержит константы только со строковыми значениями, то совместимыми считаются только константы перечисления, указанного в качестве типа.
1 2 3 4 5 6 7 8 9 10 11 12 13
enum Berrys Strawberry = 'strawberry', Raspberry = 'raspberry', Blueberry = 'blueberry', > function isBerryInStory(berry: Berrys): boolean return true; > isBerryInStory(Berrys.Strawberry); // ок isBerryInStory(123456); // Error isBerryInStory('strawberry'); // Error
Поведение не совсем очевидное, поэтому не стоит забывать об этом при использовании перечислений, в которых присутствуют константы с числовым значением в качестве типа.
Перечисление const с числовым и строковым значением¶
Перечисление enum , объявленное с помощью ключевого слова const , после компиляции не оставляет в коде привычных конструкций. Вместо этого компилятор встраивает литералы значений в места, в которых происходит обращение к значениям перечисления. Значения констант перечисления могут быть как числовыми, так и строковыми типами данных. Так же как и в обычных перечислениях, в перечислениях, объявленных с помощью ключевого слова const есть возможность создавать псевдонимы (alias) для уже объявленных констант.
Если создать два перечисления Apple и Pear , у каждого из которых будет объявлена константа Sugar с числовым значением, то на основе этих констант можно рассчитать количество сахара в яблочно-грушевом соке. Присвоив результат операции сложения количества сахара в промежуточную переменную, мы получим хорошо читаемое, задекларированное выражение.
1 2 3 4 5 6 7 8 9 10
const enum Apple Sugar = 10, > const enum Pear Sugar = 10, > let calciumInApplePearJuice: number = Apple.Sugar + Pear.Sugar;
После компиляции от перечисления не остается и следа, так как константы будут заменены числовыми литералами. Такое поведение называется inline встраивание.
let calciumInApplePearJuice = 10 + 10;
Обращение к значению через точечную нотацию требует большего времени, чем обращение к литеральному значению напрямую. Поэтому код с inline конструкциями выполняется быстрее по сравнению с кодом, в котором происходит обращение к членам объекта. Прибегать к подобному подходу рекомендуется только в тех частях кода, которые подвержены высоким нагрузкам. За счет перечисления, объявленного с ключевым словом const , исходный код будет легко читаемым, а конечный код — более производительным.
Тип enum является уникальным для TypeScript, в JavaScript подобного типа не существует.
Когда стоит применять enum?¶
Может возникнуть вопрос — «Когда использовать enum и стоит ли это делать с учетом закрепившейся привычки работы со статическими классами и константами?».
Ответ очевиден — безусловно стоит применять тогда, когда нужна двухсторонняя ассоциация строкового ключа с его числовым или строковым значением (проще говоря, карта строковый ключ — числовое значение / числовой ключ — строковое значение).
Кроме того, enum лучше всего подходит для определения дискриминантных полей, речь о которых пойдет позже.
Ну а тем, кто считает, что скомпилированная конструкция enum отягощает их код и при этом они пользовались ранее транскомпилятором Babel , то ответьте себе на вопрос — Почему вы это делали, если он добавляет в сотню раз больше лишнего кода?. Рассуждение о том, что несколько лишних строк кода испортит или опорочит программу, является пустой тратой драгоценного времени.
Поэтому если есть желание использовать enum , то делайте это. Мне не доводилось встречать приложения, в которых не было бы enum , константных классов и просто модулей с константами одновременно. И это более чем нормально.
Перечисления¶
Перечисления — это способ организовать коллекцию связанных значений. Многие другие языки программирования (C/C#/Java) имеют тип данных enum , а JavaScript — нет. Тем не менее, TypeScript имеет. Вот пример описания перечисления в TypeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13
enum CardSuit Clubs, Diamonds, Hearts, Spades, > // Пример использования var card = CardSuit.Clubs; // Предохранитель card = 'not a member of card suit'; // Ошибка : строка не может быть назначена для типа `CardSuit`
Эти значения перечислений являются числами , поэтому далее я буду называть их числовыми перечислениями.
Числовые перечисления и числа¶
TypeScript перечисления основанные на числах. Это означает, что числа могут быть назначены на экземпляр перечисления, а также всё, что совместимо с числом .
1 2 3 4 5 6 7
enum Color Red, Green, Blue, > var col = Color.Red; col = 0; // Фактически то же что и Color.Red
Числовые перечисления и строки¶
Прежде чем мы углубимся в перечисления, давайте рассмотрим генерируемый ими JavaScript, вот пример TypeScript:
1 2 3 4 5
enum Tristate False, True, Unknown, >
генерирует следующий JavaScript:
1 2 3 4 5 6
var Tristate; (function (Tristate) Tristate[(Tristate['False'] = 0)] = 'False'; Tristate[(Tristate['True'] = 1)] = 'True'; Tristate[(Tristate['Unknown'] = 2)] = 'Unknown'; >)(Tristate || (Tristate = <>));
давайте сосредоточимся на строке Tristate[Tristate[«False»] = 0] = «False»; . Внутри неё выражение Tristate[«False»] = 0 не требует особых объяснений, по факту оно присваивает свойству «False» объекта Tristate значение 0 .
Обратите внимание, что в JavaScript оператор присваивания возвращает присвоенное значение (в данном случае 0 ). Поэтому следующая операция, выполняемая средой выполнения JavaScript, это Tristate[0] = «False» . Это означает, что вы можете использовать переменную Tristate для преобразования строковой версии перечисления в число или числовой версии перечисления в строку. Это продемонстрировано ниже:
1 2 3 4 5 6 7 8 9
enum Tristate False, True, Unknown, > console.log(Tristate[0]); // "False" console.log(Tristate['False']); // 0 console.log(Tristate[Tristate.False]); // "False" потому что `Tristate.False == 0`
Изменение числа, в числовом перечислении¶
По умолчанию перечисления начинаются с 0 , а затем каждое последующее значение автоматически увеличивается на 1. В качестве примера рассмотрим следующее:
1 2 3 4 5
enum Color Red, // 0 Green, // 1 Blue, // 2 >
Однако вы можете изменить число, связанное с любым элементом перечисления, назначив его конкретным. Это показано ниже, где мы начинаем с 3 и начинаем увеличивать:
1 2 3 4 5
enum Color DarkRed = 3, // 3 DarkGreen, // 4 DarkBlue, // 5 >
СОВЕТ: Я обычно инициализирую первое перечисление с = 1 , чтобы можно было наверняка проверить значение перечисления на достоверность.
Числовые перечисления в качестве флагов¶
Одним из отличных вариантов использования перечислений является возможность использовать перечисления в качестве Флагов . Флаги позволяют вам проверить, верно ли определенное условие из набора условий. Рассмотрим следующий пример, где у нас есть набор свойств о животных:
1 2 3 4 5 6 7
enum AnimalFlags None = 0, HasClaws = 1 <0, CanFly = 1 <1, EatsFish = 1 <2, Endangered = 1 <3, >
Здесь мы используем оператор сдвига влево для перемещения 1 вокруг определенного уровня битов, чтобы получить побитовые непересекающиеся числа 0001 , 0010 , 0100 и 1000 (это десятичные числа 1 , 2 , 4 , 8 если вам интересно). Побитовые операторы | (или) / & (и) / ~ (не) являются вашими лучшими друзьями при работе с флагами и продемонстрированы ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
enum AnimalFlags None = 0, HasClaws = 1 <0, CanFly = 1 <1, > type Animal = flags: AnimalFlags; >; function printAnimalAbilities(animal: Animal) var animalFlags = animal.flags; if (animalFlags & AnimalFlags.HasClaws) console.log('животное имеет когти'); > if (animalFlags & AnimalFlags.CanFly) console.log('животное может летать'); > if (animalFlags == AnimalFlags.None) console.log('ничего'); > > let animal: Animal = flags: AnimalFlags.None >; printAnimalAbilities(animal); // ничего animal.flags |= AnimalFlags.HasClaws; printAnimalAbilities(animal); // животное имеет когти animal.flags &= ~AnimalFlags.HasClaws; printAnimalAbilities(animal); // ничего animal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly; printAnimalAbilities(animal); // животное имеет когти, животное может летать
- мы использовали |= , чтобы добавить флаги
- комбинация &= и ~ для очистки флага
- | объединить флаги
Примечание: вы можете комбинировать флаги для создания удобных сокращений в определении перечисления, например EndangeredFlyingClawedFishEating ниже:
1 2 3 4 5 6 7 8 9 10 11 12
enum AnimalFlags None = 0, HasClaws = 1 <0, CanFly = 1 <1, EatsFish = 1 <2, Endangered = 1 <3, EndangeredFlyingClawedFishEating = HasClaws | CanFly | EatsFish | Endangered, >
Строковые перечисления¶
Мы смотрели только на перечисления, где значениями являются числа . На самом деле вы также можете использовать перечисления со строковыми значениями. Например
1 2 3 4 5 6 7 8
export enum EvidenceTypeEnum UNKNOWN = '', PASSPORT_VISA = 'passport_visa', PASSPORT = 'passport', SIGHTED_STUDENT_CARD = 'sighted_tertiary_edu_id', SIGHTED_KEYPASS_CARD = 'sighted_keypass_card', SIGHTED_PROOF_OF_AGE_CARD = 'sighted_proof_of_age_card', >
С ними проще работать и проверять, так как они предоставляют выразительные / проверяемые строковые значения.
Вы можете использовать эти значения для простого сравнения строк. Например
1 2 3 4 5 6 7 8 9
// Где `someStringFromBackend` будет // '' | 'passport_visa' | 'passport' . и т. д. const value = someStringFromBackend as EvidenceTypeEnum; // Пример использования в коде if (value === EvidenceTypeEnum.PASSPORT) console.log('Вы предоставили паспорт'); console.log(value); // `passport` >
Константные перечисления¶
Если у вас есть определение перечисления, подобное следующему:
1 2 3 4 5 6 7
enum Tristate False, True, Unknown, > var lie = Tristate.False;
Строка var lie = Tristate.False компилируется в JavaScript как var lie = Tristate.False (да, вывод совпадает с вводом). Это означает, что во время выполнения потребуется поиск Tristate , а затем Tristate.False . Чтобы повысить производительность, вы можете записать enum как const enum . Это продемонстрировано ниже:
1 2 3 4 5 6 7
const enum Tristate False, True, Unknown, > var lie = Tristate.False;
генерирует следующий JavaScript:
var lie = 0;
то есть компилятор:
- Встраивает любое использование перечисления ( 0 вместо Tristate.False ).
- Не генерирует JavaScript для определения перечисления (во время выполнения отсутствует переменная Tristate ), поскольку его использование встроено.
Константные перечисления с флагом preserveConstEnums¶
Встраивание имеет очевидные преимущества в производительности. Тот факт, что во время выполнения отсутствует переменная Tristate , просто означает что компилятор помогает вам не генерировать JavaScript, который фактически не используется во время выполнения.
Однако вы, возможно, захотите, чтобы компилятор по-прежнему генерировал JavaScript-версию определения перечисления для перевода числа в строку или строки в число, как мы рассматривали выше. В этом случае вы можете использовать флаг компилятора —preserveConstEnums , и он все равно сгенерирует определение var Tristate , чтобы вы могли использовать Tristate[«False»] или Tristate[0] вручную во время выполнения, если это нужно. Это никак не влияет на встраивание.
Перечисление со статическими функциями¶
Вы можете использовать объявление перечисления + область имен , чтобы добавить статические методы в перечисление. Следующий пример демонстрирует как мы добавляем статический элемент isBusinessDay в перечисление Weekday :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
enum Weekday Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, > namespace Weekday export function isBusinessDay(day: Weekday) switch (day) case Weekday.Saturday: case Weekday.Sunday: return false; default: return true; > > > const mon = Weekday.Monday; const sun = Weekday.Sunday; console.log(Weekday.isBusinessDay(mon)); // true console.log(Weekday.isBusinessDay(sun)); // false
Перечисления расширяемы¶
ПРИМЕЧАНИЕ: расширяемые перечисления имеют значение только в том случае, если вы не используете модули. Вы должны использовать модули. Следовательно, этот раздел является последним.
Вот сгенерированный JavaScript для перечисления из известного выше примера:
1 2 3 4 5 6
var Tristate; (function (Tristate) Tristate[(Tristate['False'] = 0)] = 'False'; Tristate[(Tristate['True'] = 1)] = 'True'; Tristate[(Tristate['Unknown'] = 2)] = 'Unknown'; >)(Tristate || (Tristate = <>));
Мы уже объяснили часть Tristate[Tristate[«False»] = 0] = «False»; . Теперь обратите внимание на оборачивающий код (function (Tristate) < /*code here */ >)(Tristate || (Tristate = <>)); особенно часть (Tristate || (Tristate = <>)); . Он в основном описывает локальную переменную Tristate , которая будет либо указывать на уже определенное значение Tristate , либо инициализировать новый пустой объект <> .
Это означает, что вы можете разделить (и расширить) определение перечисления на несколько файлов. Например, ниже мы разделили определение для Color на два блока
1 2 3 4 5 6 7 8 9 10 11
enum Color Red, Green, Blue, > enum Color DarkRed = 3, DarkGreen, DarkBlue, >
Обратите внимание, что вы должны повторно инициализировать первый элемент (здесь DarkRed = 3 ) в продолжении перечисления, чтобы получить производный код, а не значения перезаписанные из предыдущего определения (то есть 0 , 1 , . и так далее). TypeScript предупредит вас, если вы этого не сделаете (сообщение об ошибке В перечислении с несколькими объявлениями только одно объявление может не использовать инициализатор для своего первого элемента ).
TypeScript здорового человека, или почему с Enum лучше
Наверное, большинство фронтенд-разработчиков в какой-то момент сталкивались с задачей внедрения TypeScript на проект. Обычно это задача выполняется не сразу, а постепенно. Сначала просто переименовываются все файлы из .js в .ts с проставлением везде типа «any», просто чтобы проект запустился, и только потом постепенно разработчики начинают заниматься планомерным переводом.
Если разработчики к тому времени не имеют серьезного опыта работы с TypeScript и изучают его по ходу дела, то очень часто перевод заканчивается на этапе создания модели данных, то есть внедрения типов и интерфейсов для всех основных сущностей, типизации API.
Зачастую, при создании типов и интерфейсов, описывая какое-то свойство, которое может принимать определенное, конечное количество строковых значений, разработчики указывают тип поля «string» или в крайнем случае перечисляют эти значения через «или».
Так, при создании интерфейса какого-либо сотрудника, у которого есть имя, возраст и должность в компании наиболее простой и быстрый вариант представлен ниже:
interface Person
Ошибок нет. Вроде бы все работает, однако какие проблемы это может создать? Если имя — это строка, которая может принимать любое значение, то должность в компании — это тоже строка, но принимать она может только вполне определенное и конечное количество строковых значений. Например, в нашей компании есть только директор и продавец. В случае, если мы попытаемся создать объект с должностью «бухгалтер», такой тип ошибки не выдаст:
const person: Person =
Самый простой и быстрый (но неправильный) способ решить эту проблему — создать условный тип и перечислить в типе все возможные значения:
type Position = 'Директор' | 'Продавец'; interface Person
Тогда умный TypeScript ругнется, когда мы попробуем создать бухгалтера:
И вроде бы проблема решена, но нет.
И, как вы наверно поняли из названия статьи, все эти проблемы можно решить, используя такую замечательную часть TypeScript, как Перечисления (Enum).
Согласно документации, Enum, это перечисления, которые позволяют разработчику определить набор именованных констант.
TypeScript предоставляет как числовые, так и строковые перечисления. В данной статье речь пойдет именно о строковых Enums.
В строковом перечислении каждый член должен быть константно инициализирован строковым литералом или другим членом строкового перечисления. Применительно для нашего случая строковое перечисление, которое мы используем вместо типа Position, будет выглядеть так:
enum Position < Director = 'Директор', Seller = 'Продавец' >interface Person
Создав таким образом перечисление возможных должностей, мы как бы обязались указывать должность сотрудника только через перечисление. То есть теперь данная запись выдаст ошибку.
Потому что теперь строка «Директор» это просто какая-то строка, не имеющая отношения к Перечислению Position.
Теперь должность везде мы указываем вот так:
const person: Person =
И если должность «Директор» у нас в фирме изменится на «Генеральный Директор», то изменение необходимо будет ввести лишь в одном месте – Enum.
enum Position
Рассмотрим два случая, когда использование Enum дает нам интересные дополнительные преимущества, помимо хорошей структуризации кода.
1. Работа с Enum, как с интерфейсами.
Допустим нам необходимо разделить сотрудников организации по должностям. Например, пусть будет интерфейс сотрудника Director и интерфейс сотрудника Seller.
interface Director < position: Position.Director; name: string; salary: number; >; interface Seller
Как и раньше, у них есть поле position, которое определено через enum. Давайте напишем функцию, которая будет принимать на вход сотрудника любого из этих двух типов и, в зависимости от значения поля position, возвращать данного сотрудника с одним из указанных типов.
function employeeTypeChecker( position: T, employee: Director | Seller ) < if (position === Position.Director) < return employee as T extends Position.Director ? Director : never >else < return employee as T extends Position.Seller ? Seller : never; >>
Теперь давайте создадим двух пользователей с неизвестным типом, но с точно определенным полем position.
const user1 = < position: Position.Seller as const, name: 'Mary', salary: 5000, product: 'Phone' >const user2 =
Обратите внимание, что у наших пользователей должность может принимать только одно из возможных значений Enum Position. И теперь, с помощью employeeTypeChecker, мы можем точно получить тип пользователя, с которым имеем дело в каждом конкретном случае.
Это стало возможным благодаря тому, что в функции employeeTypeChecker мы работаем с Enum как с интерфейсом. Мы можем применять extends, можем использовать условные типы. Если бы поле position было строкой, такое было бы невозможно.
2. Перевод enum в массив
Еще один полезный кейс, который нам дает Enum — это легкий способ получения массива всех его возможных значений. Так как Enum по своей сути это объект, то применение Object.values(Enum), дает нам массив строковых значений Enum.
Очень удобно, например, когда нам нужно дать пользователю возможность выбрать значение из всех возможных с помощью тега select.
Конечно, enum не является панацеей, и есть случаи, когда их использование нецелесообразно и правильнее просто прописать тип string. Однако я считаю, что таких случаев значительно меньше, чем кейсов, когда использование enum делает жизнь разработчика легче.
- typescript
- enum
- condition type
- javascript
- frontend
- front-end
- front-end разработка
- frontend development
- front-end development
- JavaScript
- Программирование
- TypeScript
Enum typescript зачем
Тип enum или перечисление позволяет определить набор именнованных констант, которые описывают определенные состояния.
Для определения перечисления применяется ключевое слово enum . Например, объявим следующее перечисление:
enum Season < Winter, Spring, Summer, Autumn >;
Перечисление называется 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, Spring, Summer, Autumn >;
фактически эквивалентно следующему:
enum Season < Winter=0, Spring=1, Summer=2, Autumn=3 >;
Хотя мы можем явным образом переопределить эти значения. Так, мы можем задать значение одной константы, тогда значения следующих констант будет увеличиваться на единицу:
enum Season < Winter=5, Spring, Summer, Autumn >; // 5, 6, 7, 8
Либо можно каждой константе задать свое значение:
enum Season < Winter=4, Spring=8, Summer=16, Autumn=32 >; // 4, 8, 16, 32
Также мы можем получить непосредственно текстовое значение:
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() мы можем передать числовые значения:
welcome(1); // Добрый вечер
Либо даже определить параметр функции как числовой и передавать константы числового перечисления:
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); // Добрый вечер
При использовании строковых перечислений в отличие от числовых мы не можем передать переметру произвольную строку:
welcome("Привет, ты спишь?")
В этом случе компилятор выкатит нам ошибку при компиляции.
В то же время если параметр представляет тип string , то такому параметру можно передавать как строки, так и константы строкового перечисления:
enum DayTimeMessage < Morning = "Доброе утро", Evening = "Добрый вечер" >; function welcome(message: string) < console.log(message); >let mes: DayTimeMessage = DayTimeMessage.Morning; welcome(mes); // Доброе утро welcome(DayTimeMessage.Evening); // Добрый вечер