Перечисления были введены в TypeScript, чтобы обеспечить способ определения набора именованных констант, которые можно присвоить переменной или использовать в качестве типа. Перечисления позволяют разработчикам определять набор связанных значений со значимыми и описательными именами, что упрощает чтение и понимание кода.
Перечисления в TypeScript предоставляют ряд преимуществ:
- Безопасность типов*: перечисления сами по себе являются типами, а это означает, что TypeScript может проверять правильность значений, присвоенных переменным перечисления.
- Поддержка IDE: при использовании перечислений в TypeScript среды IDE могут предлагать возможные значения на основе определения перечисления.
Читаемость: перечисления позволяют разработчикам использовать описательные имена для значений, что делает код более читабельным и понятным.
Есть несколько особенностей перечислений в TypeScript, которые могут быть не столь очевидны:
- Обратное сопоставление: перечисления в TypeScript поддерживают обратное сопоставление, что означает, что вы можете получить значение перечисления из соответствующего строкового представления. Например:
enum Color { Red = 1, Green, Blue } const colorName = "Green"; const colorValue = Color[colorName]; // 2
Это может быть полезно в случаях, когда вам нужно преобразовать строковое значение в соответствующее значение перечисления.
2. Гетерогенные перечисления: TypeScript позволяет перечислениям иметь как строковые, так и числовые значения и даже значения разных типов в одном перечислении. Однако, как правило, это не рекомендуется, так как это может привести к путанице и неожиданному поведению. Например:
enum MyEnum{ A = 1, B = "B", C = true } const a = MyEnum.A; // 1 const b = MyEnum.B; // "B" const c = MyEnum.C; // true
Хотя это возможно, обычно лучше использовать перечисления с согласованными типами, чтобы избежать путаницы.
3. Перечисления как битовые флаги. Перечисления в TypeScript можно использовать как битовые флаги, где каждое значение перечисления представляет один бит в большем двоичном значении. Это может быть полезно в случаях, когда вам нужно представить несколько логических флагов в одном значении. Например:
enum Flags { None = 0, A = 1 << 0, B = 1 << 1, C = 1 << 2 } const flagValue = Flags.A | Flags.C; // 5 (binary 101) const hasFlagA = flagValue & Flags.A; // true const hasFlagB = flagValue & Flags.B; // false const hasFlagC = flagValue & Flags.C; // true
Этот подход может быть более эффективным, чем использование отдельных логических значений, но с ним может быть и сложнее работать.
Это некоторые из уникальных особенностей перечислений в TypeScript, и хотя они могут не использоваться повсеместно, в некоторых случаях они могут быть полезны.
Темная сторона перечислений
Но даже если перечисления обладают уникальными свойствами и допускают уникальные способы использования, в большинстве случаев их лучше избегать.
- Большое использование памяти: перечисления в TypeScript представлены как объекты, что может привести к значительному использованию памяти, если перечисление имеет большое количество значений.
- Возможные конфликты имен: перечисления используют то же пространство имен, что и другие идентификаторы в TypeScript, а это означает, что существует вероятность конфликтов имен, если одно и то же имя используется как для перечисления, так и для другого идентификатора.
- Неуникальные значения: в TypeScript можно присвоить одно и то же значение нескольким членам перечисления. Это может вызвать путаницу и неожиданное поведение при использовании перечисления.
- Ограниченная расширяемость: после определения перечисления его нельзя расширить или изменить. Это может затруднить адаптацию перечисления к меняющимся требованиям или добавление к нему новых значений.
- Чрезмерное использование перечислений: перечисления следует использовать с осторожностью и только тогда, когда значения действительно фиксированы и известны во время компиляции. Чрезмерное использование перечислений может привести к тому, что код будет трудно читать и поддерживать.
- Использование перечислений для числовых значений: перечисления не предназначены для использования в качестве замены числовых значений. Например, определение перечисления со значениями 0, 1, 2 и т. д. не является предполагаемым вариантом использования перечислений.
- Использование строковых значений в перечислениях: хотя TypeScript позволяет перечислениям использовать строковые значения, обычно это не рекомендуется. Строки менее эффективны, чем числовые значения, и могут быть более подвержены опечаткам и ошибкам.
- Перечисления не могут быть расширены
Когда код TypeScript компилируется в JavaScript, перечисления преобразуются в объекты со значениями перечислений в качестве свойств. Это может привести к нескольким потенциальным недостаткам.
Увеличенный размер файла. Перечисления могут добавлять дополнительный код в скомпилированный файл JavaScript, что может увеличить размер файла. Это может не быть серьезной проблемой для небольших перечислений, но более крупные перечисления могут добавить заметные накладные расходы.
Увеличение использования памяти. Перечисления в JavaScript — это объекты со свойствами, что означает, что они занимают больше памяти, чем простые значения, такие как числа или строки. Если у вас большое перечисление или вы часто используете перечисления, это может повлиять на производительность.
Значения перечисления не уникальны. В отличие от TypeScript, где значения перечисления гарантированно уникальны, в JavaScript значения перечисления могут быть перезаписаны или доступны под разными именами. Это может привести к неожиданному поведению, если не использовать его осторожно.
Ограниченная поддержка перечислений в старых браузерах. Некоторые старые браузеры могут не полностью поддерживать функции ES6, используемые для реализации перечислений в TypeScript, что может привести к проблемам совместимости. Хотя это становится менее серьезной проблемой по мере прекращения использования старых браузеров, все же стоит подумать о том, нужна ли вам поддержка широкого спектра браузеров.
Сложность отладки. Когда ошибка возникает в коде, использующем перечисления, сообщение об ошибке может ссылаться на базовые числовые значения перечисления, а не на именованные константы, что может затруднить выявление проблемы.
Константные перечисления нельзя транспилировать с помощью Babel: решение этой проблемы состоит в том, чтобы вручную добавить подключаемый модуль для этого.
Несколько примеров неправильного использования перечислений:
- Чрезмерное использование перечислений:
enum DaysOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday } // In this example, an enum is used to define the days of the week. However, this enum is not necessary as the days of the week are already well-known and fixed. Using an enum in this case adds unnecessary complexity to the code // Same goes to use string values for this enums. It's just redundant.
2. Использование перечислений для числовых значений:
enum Age { Zero = 0, One, Two, Three, Four } // In this example, an enum is used to define ages, but it is not appropriate as ages are typically represented by numeric values, not named constants. // Also if this will be used as type it will allow values that exceed amount of values in enum let myNumber: Age = 5; // won't cause any error, even if we have only 0,1,2,3,4 as allowed values.
Как заменить
В определенных ситуациях перечисления могут быть заменены другими функциями языка.
- Типы объединения: Типы объединения могут использоваться для представления набора именованных значений. Например, вместо определения перечисления для цветов вы можете определить тип объединения:
type Color = 'Red' | 'Green' | 'Blue'; let color: Color = 'Red';
2. Литералы объектов: вместо использования перечисления для представления набора именованных значений вы можете использовать литерал объекта:
const Color = { Red: '#FF0000', Green: '#00FF00', Blue: '#0000FF', } as const; type Color = typeof Color[keyof typeof Color]; let color: Color = 'Red';
3. Типы строковых литералов. Типы строковых литералов также могут использоваться для представления набора именованных значений. Например, вместо определения перечисления для кодов состояния HTTP вы можете определить тип строкового литерала:
type HttpStatusCode = '200' | '201' | '400' | '401' | '404' | '500'; let statusCode: HttpStatusCode = '200';
4. Иерархии классов. Иерархии классов могут использоваться для представления набора связанных значений. Например, вместо определения перечисления для разных типов животных вы можете определить иерархию классов:
abstract class Animal { abstract name: string; abstract makeSound(): void; } class Dog extends Animal { name = 'Dog'; makeSound() { console.log('Woof!'); } } class Cat extends Animal { name = 'Cat'; makeSound() { console.log('Meow!'); } } let dog = new Dog(); dog.makeSound(); // logs "Woof!"
В заключение, перечисления могут быть заменены другими функциями языка в определенных ситуациях. Выбирая правильную языковую функцию для конкретной ситуации, вы можете улучшить читабельность, ремонтопригодность и производительность вашего кода.
Используемые источники:
В Typescript есть объединения, значит, перечисления излишни?