TypeScript
February 29

Использовать Enum в Typescript опасно

Эксперты TypeScript рекомендуют избегать использования ENUM.

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

Увеличение кода

Это одна из причин, по которой рекомендуется избегать их использования, связана с генерацией кода во время компиляции приложения. Enum создают дополнительный код на этапе компиляции, что увеличивает размер конечного файла. Это может негативно сказаться на скорости загрузки и производительности приложения. Рассмотрим пример:

enum Roles {
  Admin,
  Writer,
  Reader
}

после сборки кода мы получим такую структуру:

var Roles;
(function (Roles) {
    Roles[Roles["Admin"] = 0] = "Admin";
    Roles[Roles["Writer"] = 1] = "Writer";
    Roles[Roles["Reader"] = 2] = "Reader";
})(Roles|| (Roles = {}));

Кажется, что это незначительная деталь, но представьте, что у вас есть файлы, которые общие для "Frontend" и "Backend", и в итоге вы получаете довольно тяжелые пакеты. Ладно, это одна проблема, и мы можем справиться с ней, навязывая константы. Но есть еще неприятные моменты:

Enum не защищают от несоответствия типа

Обычные числовые перечисления, такие как перечисление, где устанавливаются строковые значения, не являются типобезопасными. Если посмотреть на перечисление Roles из предыдущего примера, функция, которая принимает роли пользователей, также принимает любое числовое значение.

enum Roles {
  Admin,
  Writer,
  Reader
}

declare function hasAccess(role: Roles): void;

hasAccess(10);
// ☝️ Worst of all, this is ok! 😱

При вызове функции hasAccess(10) передается числовое значение, которое не является частью перечисления Roles. Это допускается в TypeScript, и это то, что можно считать проблемой, поскольку это позволяет вводить неожиданные и непроверенные значения, что может вызывать проблемы с безопасностью и производительностью приложения.

Строковые Enum являются именованными типами

В мире, где структурные типы распространены, перечисления выбирают быть именованным типом. Это означает, что даже если значения являются допустимыми и поддерживаемыми, их нельзя передать функции или объекту, где ожидается строковое перечисление. Рассмотрим следующий пример:

enum Roles {
  Admin = 'admin',
  Writer = 'writer',
  Reader = 'reader'
}

declare function hasAccess(role: Roles): void;

hasAccess('admin') // Invalid.
hasAccess(Roles.Admin) // Valid.

Перечисления являются именованным типом и принимают только специфические для перечисления значения, а не совместимые или похожие значения, что может привести к проблемам с совместимостью.

Решение

Решением будет использование объектов, что гораздо более безопасно и гарантирует совместимость. Давайте рассмотрим следующий пример:

const Roles = {
  Admin: "admin",
  Writer: "writer",
  Reader: "reader"
} as const;

// Convert object key in a type
type RoleKeys = typeof Roles[keyof typeof Roles]

declare function hasAccess(role: RoleKeys): void;

// 💥 Error!
move('guest');

// 👍 Great!
move('admin');

// 👍 Also great!
move(Roles.Admin);

Enum могут показаться полезным инструментом для определения набора констант, но на самом деле они очень опасны. Чрезмерное использование Enum может привести к проблемам с размером кода, проблемам безопасности, масштабируемости и поддержке кода.

Вместо использования перечислений лучше выбрать объекты или типы. Объекты гибки и масштабируемы, что означает, что их можно изменять во время выполнения и добавлять новые значения. Типы также гибки и масштабируемы, и также предлагают лучшую ясность и читаемость кода. Кроме того, объекты и типы менее подвержены ошибкам и проблемам безопасности по сравнению с Enum.

Один из вариантов обхода описан на Habr: https://habr.com/ru/articles/784862/

Это вольный перевод оригинального материала