Enum typescript что это
Война с TypeScript или покорение Enum
Предыстория
Пол года назад в нашей компании было принято решение о переходе на более новые и модные технологии. Для этого сформировали группу специалистов, которая должна была: определиться с технологическим стеком, на базе этого стека сделать мост к легаси коду и, наконец, перевести часть старых модулей на новые рельсы. Мне посчастливилось попасть в эту группу. Клиентская кодовая база примерно в один миллион строк кода. В качестве языка мы выбрали TypeScript. GUI подложку решили делать на vue в купе с vue-class-component и IoC.
Но история не о том, как мы избавлялись от легаси кода, а об одном маленьком инциденте, который вылился в настоящую войну знаний. Кому интересно, добро пожаловать под кат.
Знакомство с проблемой
Спустя пару месяцев после старта, рабочая группа неплохо освоилась с новым стеком и уже успела перевести на него часть старого кода. Можно даже сказать, что набралась некоторая критическая масса мяса, когда нужно было остановиться, перевести дух и взглянуть на то, что мы накодили.
Мест, которые требовали глубокой проработки, как Вы понимаете, хватало. Но из всего важного, по иронии судьбы, меня ничего не цепляло. Не цепляло как разработчика. А вот одно из неважного, наоборот, не давало покоя. Меня до безумия раздражало то, как мы работаем с данными типа перечисление. Не было никакого обобщения. То встретишь отдельный класс с набором требуемых методов, то найдешь два класса, для того же самого, а то и вовсе нечто загадочное и магическое. И винить здесь некого. Темп который мы взяли для избавления от легаси был слишком велик.
Подняв тему с перечислениями среди коллег, я получил поддержку. Оказалось, что не только я не доволен отсутствием единого подхода по работе с ними. В тот момент, мне привиделось, что за пару часов кодинга я достигну желаемого результата и вызвался исправить сложившуюся ситуацию. Но как я тогда ошибался…
1. Декоратор
С чего начать? На ум приходило только одно: взять за основу Java-подобное перечисление. Но так как мне хотелось выпендриться перед коллегами, я решил отказаться от классического наследования. Вместо него воспользоваться декоратором. Декоратор к тому же, можно было бы применить с аргументами, для того, чтобы придать перечислениям требуемую функциональность легко и непринужденно. Кодинг не отнял много времени и через пару часиков я уже имел, что-то похожее на это:
И здесь меня постигла первая неудача. Оказалось, что с помощью декоратора нельзя изменить тип. На эту тему у Microsoft есть даже обращение: Class Decorator Mutation. Когда я говорю, что нельзя изменить тип, я имею ввиду, что Ваша IDE ничего об этом не узнает и никаких подсказок и адекватных автодополнений не предложит. А тип менять можно сколько угодно, только толку от этого…
2. Наследование
Как я не старался уговаривать самого себя, но мне пришлось вернуться к идее создания перечислений на базе общего класса. Да и что в этом такого? Я был раздосадован самим собой. Время то идет, парни из группы фигачат дай бог, а я тут на декораторы время трачу. Можно было вообще за час запилить enum и идти дальше. Значит так тому и быть. Быстро накидал код базового класса Enumerable и вздохнул, почувствовав облегчение. Закинул драфт в общий репозиторий и попросил коллегу проверить решение.
Но трагикомедия набирала полный ход. У меня на машине был установлен TypeScript версии 2.6.2, именно та версия, в которой имелся бесценный баг. Бесценный, потому что не баг, а фитча. Голос из соседней комнаты прокричал, что у него ничего не собирается. Ошибка при компиляции (транспиляции). Я не поверил собственным ушам, ибо всегда собираю проект, перед пушем, даже если это драфт. А внутренний голос прошептал: это фиаско, братан.
После недолгих разбирательств понял, что дело в версии TypeScript. Оказалось, что если имя дженерика у класса совпадало с именем дженерика указанного в статическом методе, то компилятор рассматривал это как один тип. Но как бы там не было, сейчас это уже часть истории той войны за знание TypeScript.
В сухом остатке: проблема с перечислениями как была так и осталась. Моя печаль…
Примечание: не могу воспроизвести такое поведение у себя сейчас с 2.6.2, возможно с версией ошибся или чего-то не дописал в тестовых примерах. А запрос на описанную выше проблему Allow static members to reference class type parameters был отклонен.
3. Функция кастования
Несмотря на то, что кривое решение имелось, с явным указанием типа класса перечисления в статических методах, например так, State.valueOf (), оно никого не устраивало и прежде всего меня. На некоторое время я даже отложил в сторону долбаные перечисления и потерял уверенность в том, что вообще смогу решить эту проблему.
Морально восстановив силы, я порыскал в интернете трюки с TypeScript, посмотрел кто от чего страдает, почитал документацию по языку еще раз, на всякий случай, и решил, во чтобы то не стало, довести дело до конца. Семь часов непрерывных экспериментов, не отрываясь ни на что, даже на кофе, дали свой результат. Всего одна функция, состоящая из одной строчки кода, расставила все на свои места.
А само объявление Java-подобного перечисления теперь выглядит так:
Не обошлось и без курьеза, с лишним импортом IStaticEnum, который нигде не используется (см пример выше). В той самой злополучной версии TypeScript 2.6.2 нужно указывать его явно. Баг на тему здесь.
Итого
Если долго мучаться, что-нибудь получится. Ссылка на гитхаб с результатом проделанной работы здесь. Для себя я открыл, что TypeScript — это язык с большими возможностями. Этих возможностей так много, что в них можно утонуть. А кто не хочет идти ко дну, учится плавать. Если вернуться к теме перечислений, то можно увидеть как с ними работают другие:
Руководство по перечислению TypeScript
Перечисления (или перечисления) — это поддерживаемый тип данных в TypeScript. Перечисления используются в большинстве объектно-ориентированных языков программирования, таких как Java и C #, а теперь доступны и в TypeScript. Это одна из немногих функций TypeScript, которая не является расширением JavaScript на уровне типов. Перечисления позволяют определять набор именованных констант. Их использование упрощает документирование намерений или создание набора отдельных случаев. Сегодня мы рассмотрим основы перечислений TypeScript, а также варианты использования, различные типы перечислений и следующие шаги для вашего обучения.
Что такое enum в Typescript?
Перечисления TypeScript позволяют определять набор именованных констант. Их использование может упростить документирование намерений или создание набора отдельных случаев. Многие языки программирования, такие как C, C # и Java, имеют enumтип данных, а JavaScript — нет. Однако TypeScript делает. TypeScript имеет как числовые, так и строковые перечисления.
Синтаксис перечислений следующий:
Прежде чем мы более подробно рассмотрим несколько различных типов перечислений, давайте обсудим преимущества перечислений в TypeScript.
Зачем использовать перечисления в TypeScript?
Перечисления — отличный способ организовать ваш код в TypeScript. Давайте посмотрим на некоторые плюсы:
Перечисления против альтернатив
Хотя использование перечислений TypeScript дает множество преимуществ, в некоторых случаях вам не следует их использовать, например:
Теперь давайте углубимся в некоторые типы перечислений.
Числовые перечисления
Числовые перечисления хранят строковые значения как числа. Их можно определить с помощью enumключевого слова. Допустим, вы хотите хранить набор разных типов автомобилей. В следующем примере показано числовое перечисление в TypeScript:
Значение перечисления CarTypeимеет четыре значения: Honda, Toyota, Subaru и Hyundai. Значения перечисления начинаются с нуля и увеличиваются на единицу для каждого члена, что будет выглядеть примерно так:
При желании вы можете самостоятельно инициализировать первое числовое значение следующим образом:
В приведенном выше примере мы инициализировали первый член Hondaчисловым значением, равным единице. Остальные числа будут увеличены на единицу.
Примечание : нет необходимости назначать последовательные значения вашим членам перечисления. Вы можете присвоить им любые значения, какие захотите.
Перечисления строк
Перечисления строк аналогичны числовым перечислениям, но их значения перечисления инициализируются строковыми значениями вместо числовых значений. Строковые перечисления лучше читаются, чем числовые, что упрощает отладку ваших программ.
В следующем примере используется та же информация, что и в примере числового перечисления, но она представлена в виде строкового перечисления:
В этом примере мы определили строковое перечисление CarTypeс теми же значениями, что и числовое перечисление, за исключением того, что значения перечисления инициализируются как строковые литералы.
Примечание. Строковые значения перечисления необходимо инициализировать индивидуально.
Гетерогенные перечисления
Гетерогенные перечисления содержат как числовые, так и строковые значения. Вот пример:
Обратное отображение с перечислениями
Вы знаете, что значения num можно получить, используя соответствующие им значения членов перечисления. При обратном отображении вы можете получить доступ к значению члена и имени члена из его значения. Давайте посмотрим на пример:
Карманная книга по TypeScript. Часть 2. Типы на каждый день
Мы продолжаем серию публикаций адаптированного и дополненного перевода «Карманной книги по TypeScript «.
Обратите внимание: для большого удобства в изучении книга была оформлена в виде прогрессивного веб-приложения.
Массивы
Обратите внимание: [number] — это другой тип, кортеж (tuple).
Тип any может быть полезен в случае, когда мы не хотим писать длинное определение типов лишь для того, чтобы пройти проверку.
noImplicitAny
Обычно, мы хотим этого избежать, поскольку any является небезопасным с точки зрения системы типов. Установка флага noImplicitAny позволяет квалифицировать любое неявное any как ошибку.
Аннотации типа для переменных
Однако, в большинстве случаев этого делать не требуется, поскольку TS пытается автоматически определить тип переменной на основе типа ее инициализатора, т.е. значения:
Функции
В JS функции, в основном, используются для работы с данными. TS позволяет определять типы как для входных (input), так и для выходных (output) значений функции.
Аннотации типа параметров
При определении функции можно указать, какие типы параметров она принимает:
Вот что произойдет при попытке вызвать функцию с неправильным аргументом:
Обратите внимание: количество передаваемых аргументов будет проверяться даже при отсутствии аннотаций типа параметров.
Аннотация типа возвращаемого значения
Также можно аннотировать тип возвращаемого функцией значения:
Анонимные функции
Анонимные функции немного отличаются от обычных. Когда функция появляется в месте, где TS может определить способ ее вызова, типы параметров такой функции определяются автоматически.
Типы объекта
Объектный тип — это любое значение со свойствами. Для его определения мы просто перечисляем все свойства объекта и их типы. Например, так можно определить функцию, принимающую объект с координатами:
Опциональные свойства
Объединения (unions)
Определение объединения
Объединение — это тип, сформированный из 2 и более типов, представляющий значение, которое может иметь один из этих типов. Типы, входящие в объединение, называются членами (members) объединения.
Реализуем функцию, которая может оперировать строками или числами:
Работа с объединениями
Решение данной проблемы заключается в сужении (narrowing) объединения. Например, TS знает, что только для string оператор typeof возвращает ‘string’ :
Другой способ заключается в использовании функции, такой как Array.isArray :
Синонимы типов (type aliases)
Что если мы хотим использовать один и тот же тип в нескольких местах? Для этого используются синонимы типов:
Синонимы можно использовать не только для объектных типов, но и для любых других типов, например, для объединений:
Обратите внимание: синонимы — это всего лишь синонимы, мы не можем создавать на их основе другие «версии» типов. Например, такой код может выглядеть неправильным, но TS не видит в нем проблем, поскольку оба типа являются синонимами одного и того же типа:
Интерфейсы
Определение интерфейса — это другой способ определения типа объекта:
Разница между синонимами типов и интерфейсами
Пример расширения интерфейса:
Пример расширения типа с помощью пересечения (intersection):
Пример добавления новых полей в существующий интерфейс:
Тип не может быть изменен после создания:
Утверждение типа (type assertion)
Для утверждения типа можно использовать другой синтаксис (е в TSX-файлах):
TS разрешает утверждения более или менее конкретных версий типа. Это означает, что преобразования типов выполнять нельзя:
Иногда это правило может быть слишком консервативным и мешать выполнению более сложных валидных преобразований. В этом случае можно использовать двойное утверждение: сначала привести тип к any (или unknown ), затем к нужному типу:
Литеральные типы (literal types)
Вот как TS создает типы для литералов:
Сами по себе литеральные типы особой ценности не представляют:
Но комбинация литералов с объединениями позволяет создавать более полезные вещи, например, функцию, принимающую только набор известных значений:
Числовые литеральные типы работают похожим образом:
Разумеется, мы можем комбинировать литералы с нелитеральными типами:
Предположения типов литералов
При инициализации переменной с помощью объекта, TS будет исходить из предположения о том, что значения свойств объекта в будущем могут измениться. Например, если мы напишем такой код:
Тоже самое справедливо и в отношении строк:
Существует 2 способа решить эту проблему.
null и undefined
Оператор утверждения ненулевого значения (non-null assertion operator)
Перечисления (enums)
Перечисления позволяют описывать значение, которое может быть одной из набора именованных констант. Использовать перечисления не рекомендуется.
Редко используемые примитивы
bigint
Данный примитив используется для представления очень больших целых чисел BigInt :
Подробнее о BigInt можно почитать здесь.
symbol
Данный примитив используется для создания глобально уникальных ссылок с помощью функции Symbol() :
Подробнее о символах можно почитать здесь.
Облачные серверы от Маклауд идеально подходят для разработки на TypeScript.
Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!
Вводный курс по TypeScript
Авторизуйтесь
Вводный курс по TypeScript
TypeScript — это расширенная версия JavaScript, главной целью которого является упрощение разработки крупных JS-приложений. Этот язык добавляет много новых принципов — классы, дженерики, интерфейсы, статические типы, что позволяет разработчикам использовать разные инструменты, такие как статический анализатор или рефакторинг кода.
Стоит ли использовать TypeScript?
В первую очередь возникает вопрос: а какие преимущества у этого языка?
В каких случаях стоит использовать TypeScript?
Хоть выше были приведены достаточные аргументы к использованию этого языка, TypeScript не является универсальным решением. В каких же случаях лучше всего подходит этот язык?
Установка TypeScript
Установить TypeScript совсем не сложно — достаточно загрузить его через пакетный менеджер npm и создать TypeScript-файл:
После его установки можно сразу перейти к рассмотрению возможностей этого языка и его синтаксиса.
Типы переменных
Number
Все числовые переменные в TypeScript существуют в виде числа с плавающей запятой. Числовой тип получают даже двоичные и шестнадцатеричные числа:
String
Как и другие языки, TypeScript использует тип String для хранения текстовой информации:
Можно создавать и многострочные переменные, а также в строки можно вставлять выражения, если выделить строку символами « :
Boolean
Куда же без одного из основных типов данных:
Присвоение типов
Одиночный тип переменной
Простой пример, где присваивается значение переменной типа String :
Такой способ действителен для всех типов данных.
Мультитип переменной
В коде выше переменной назначается два типа: строчный и численный. Теперь переменной можно присваивать как текстовые данные, так и числовые.
Проверка типов
Ниже будут описаны два основных (на деле их существует больше) способа проверки типа переменной.
Typeof
Команда typeof работает только с базовыми типами данных. Это означает, что эта команда может определить только типы, описанные выше.
Instanceof
Тип Assertions
Иногда приходится преобразовывать (кастовать) переменную одного типа в другой тип. Особенно часто это случается, когда переменную типа any (или другого произвольного типа) нужно передать в функцию, которая принимается аргумент определённого типа.
Существует много решений этой задачи, но ниже будут описано два самых популярных из них.
Ключевое слово as
Чтобы кастовать переменную, нужно после оператора as написать тип, в который переводится переменная.
Оператор <>
Этот код работает идентично предыдущему — разница только синтаксическая.
Примитивный Тип Enum
При создании приложений тяжело обойтись без большого количества специальных конфигурационных значений. Подобные значения разработчики выносят в отдельные классы со статическими свойствами или модули с константами, избавляя таким образом свой код от магических значений.
TypeScript привносит новую синтаксическую конструкцию называемую Enum (перечисление). enum представляет собой набор логически связанных констант, в качестве значений которых могут выступать как числа, так и строки.
Enum (enum) примитивный перечисляемый тип
Перечисления с числовым значением
Идентификаторы-имена для перечислений enum принято задавать во множественном числе. В случае, когда идентификаторам констант значение не устанавливается явно, они ассоциируются с числовым значениями, в порядке возрастания, начиная с нуля.
Также можно установить любое значение вручную.
Если указать значение частично, то компилятор будет стараться соблюдать последовательность.
Вдобавок ко всему enum позволяет задавать псевдонимы (alias). Псевдонимам устанавливается значение константы, на которую они ссылаются.
При обращении к константе перечисления через точечную нотацию, будет возвращено значение. А при обращении к перечислению с помощью скобочной нотации и указания значения в качестве ключа, будет возвращено строковое представление идентификатора константы.
6 шаг. Теперь проделаем, то же самое для двух других констант.
7 шаг. Теперь превратим функции initialization в самовызывающееся функциональное выражение и лучше анонимное.
8 шаг. И перенесем инициализацию объекта прямо на место вызова.
Перечисление готово. Осталось сравнить созданное перечисление с кодом полученным в результате компиляции.
И снова взглянем на получившийся в результате компиляции код. Можно увидеть, что псевдоним создается так же, как обычная константа, но в качестве значения ему присваивается значение идентичное константе на которую он ссылается.
Перечисления со строковым значением
Но в случае, когда константам присваиваются строки, ассоциируется только ключ со значением. Обратная ассоциация (значение-ключ) — отсутствует. Простыми словами, по идентификатору (имени константы) можно получить строковое значение, но по строковому значению получить идентификатор (имя константы) невозможно.
Тем не менее остается возможность создавать псевдонимы (alias).
И снова изучим скомпилированный код. Можно убедится, что псевдонимы создаются так же, как и константы. А значение присваиваемое псевдонимам идентично значению констант на которые они ссылаются.
Смешанное перечисление (mixed enum)
Если в одном перечислении объявлены числовые и строковые константы, то такое перечисление называется смешанным (mixed enum).
Со смешанным перечислением связаны две неочевидные особенности.
Первая из них заключается в том, что константам, которым значение не задано явно, присваивается числовое значение по правилам перечисления с числовыми константами.
Для разрешения этой проблемы в смешанном перечислении, константе, которая была объявлена после константы со строковым значением, необходимо задавать значение явно.
Перечисление в качестве типа данных
Функцию, тип параметра которой является смешанным перечислением, благополучно получится вызвать как с константой перечисления в качестве аргумента, так и с любым числом. Вызвать эту же функцию с идентичной константе перечисления строкой уже не получится.
Если перечисление содержит константы только со строковыми значениями, то совместимыми считаются только константы перечисления указанного в качестве типа.
Поведение не совсем очевидное, поэтому не стоит забывать об этом при использовании перечислений в которых присутствуют константы с числовым значением в качестве типа.
Перечисление const с числовым и строковым значением
После компиляции от перечисления не остается и следа, так как константы будут заменены числовыми литералами. Такое поведение называется inline встраивание.
Тип enum является уникальным для TypeScript, в JavaScript подобного типа не существует.
Когда стоит применять enum?
Ответ очевиден — безусловно стоит применять тогда, когда нужна двухсторонняя ассоциация строкового ключа с его числовым или строковым значением (проще говоря, карта строковый ключ — числовое значение_числовой ключ — строковое значение_).
Кроме того, enum лучше всего подходит для определения дискриминантных полей речь о которых пойдет позже.