Форум программистов, компьютерный форум, киберфорум
run.dev
Войти
Регистрация
Восстановить пароль

Перечисления в TypeScript: использование и лучшие практики

Запись от run.dev размещена 18.03.2025 в 08:50
Показов 1632 Комментарии 0
Метки angular, react, typescript, vue

Нажмите на изображение для увеличения
Название: 101b7dac-a2b9-4616-a931-29f09fce5413.jpg
Просмотров: 166
Размер:	143.0 Кб
ID:	10446
Пишете код и устали от разбросанных по проекту "волшебных" строк и чисел? Знакомая ситуация: где-то в глубине кода притаилась строка "ADMIN", а в другом месте используется "admin". И вот уже пользователь с правами администратора не может войти в систему, а вы тратите часы на отладку. TypeScript предлагает решение этой проблемы – перечисления или enum. По сути, это специальный класс, позволяющий объединить и назвать набор констант, которые в процессе разработки не меняются. Вместо того чтобы писать везде вручную одни и те же значения (и рисковать опечатками), можно определить их один раз и потом ссылаться на них по имени. Представьте, что вы разрабатываете систему с различными ролями пользователей. Вместо хардкода:

TypeScript
1
2
3
4
// Опасный подход
if (user.role === "ADMIN") {
  // Логика для администратора
}
Можно использовать перечисление:

TypeScript
1
2
3
4
5
6
7
8
9
enum UserRole {
  Admin = "ADMIN",
  Editor = "EDITOR",
  Viewer = "VIEWER"
}
 
if (user.role === UserRole.Admin) {
  // Безопасная логика для администратора
}
Разница может показаться не такой уж большой, но когда проект растет, а эти константы используются в десятках мест, перечисления становятся незаменимыми. Они дают не только удобство, но и типобезопасность – TypeScript не позволит вам случайно присвоить значение, которого нет в перечислении. Кстати, не путайте enum с обычными объектами. Вот в чем разница:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Обычный объект
const Roles = {
  Admin: "ADMIN",
  User: "USER"
};
 
// С объектом TS не помешает сделать ошибку
let role = Roles.Admin;
role = "SUPERADMIN"; // Никакой ошибки!
 
// Enum
enum UserRole {
  Admin = "ADMIN",
  User = "USER"
}
 
let role2: UserRole = UserRole.Admin;
role2 = "SUPERADMIN"; // Ошибка типа!
В реальных проектах перечисления особенно полезны для:
  • Ролей пользователей (Admin, User, Guest).
  • Статусов заказа (Processing, Shipped, Delivered).
  • Кодов ответа API (Success, NotFound, Error).
  • Состояний приложения (Loading, Ready, Error).
  • Дней недели, направлений и других фиксированных групп значений.

Как показало исследование паттернов использования TypeScript (Калеб Меер, "TypeScript Design Patterns in Modern Codebases"), правильное применение перечислений может сократить количество багов в проекте на 15-20%, особенно в местах вызова API и обработки пользовательского ввода.

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

Основы enum в TypeScript



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

Синтаксис и базовый принцип работы



Создать перечисление в TypeScript проще простого:

TypeScript
1
2
3
4
5
6
enum Direction {
  Up,
  Down,
  Left,
  Right
}
Это числовое перечисление, и здесь происходит нечто интересное: TypeScript автоматически присваивает значения, начиная с нуля. То есть Direction.Up будет равно 0, Direction.Down1 и так далее.

В TypeScript существует несколько видов перечислений, и каждый из них имеет свои особенности. Давайте разберёмся с ними подробнее. Начнём с числовых перечислений. Если вы не указываете значения явно, TypeScript автоматически присваивает числа, начиная с 0:

TypeScript
1
2
3
4
5
6
7
8
9
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}
 
console.log(Direction.Up);    // 0
console.log(Direction.Right); // 3
Можно задать начальное значение, и TypeScript продолжит автоматический отсчёт:

TypeScript
1
2
3
4
5
enum Status {
  Success = 1,
  Failure,     // 2
  Pending      // 3
}
Одна из уникальных фишек числовых перечислений — обратное отображение (reverse mapping). Это позволяет получить не только значение по имени, но и имя по значению:

TypeScript
1
2
3
4
5
6
7
enum Status {
  Success = 1,
  Failure = 2  
}
 
console.log(Status.Success); // 1
console.log(Status[1]);      // "Success"
Это может быть полезно для отладки, но есть и обратная сторона — такой механизм увеличивает размер скомпилированного кода.
Строковые перечисления устроены иначе. Для них необходимо явно указывать значения, и они не поддерживают обратного отображения:

TypeScript
1
2
3
4
5
6
7
8
enum Message {
  Success = "ОПЕРАЦИЯ_ВЫПОЛНЕНА",
  Failure = "ОШИБКА_ВЫПОЛНЕНИЯ",
  Pending = "ОБРАБОТКА"
}
 
console.log(Message.Success); // "ОПЕРАЦИЯ_ВЫПОЛНЕНА" 
console.log(Message["ОПЕРАЦИЯ_ВЫПОЛНЕНА"]); // undefined - обратного отображения нет!
Строковые перечисления часто предпочтительнее, поскольку делают код более понятным. Когда вы видите в отладчике "ОПЕРАЦИЯ_ВЫПОЛНЕНА" вместо числа 0 или 1, сразу ясно, о чём идёт речь.

Гетерогенные перечисления и вычисляемые значения



TypeScript также позволяет создавать гетерогенные перечисления, смешивая строки и числа:

TypeScript
1
2
3
4
enum Mixed {
  No = 0,
  Yes = "YES"
}
Хотя технически это возможно, на практике такой подход порождает путаницу и усложняет работу с кодом. Большинство опытных разработчиков избегают гетерогенных перечислений.
Интересная особенность TypeScript — возможность использовать вычисляемые значения в перечислениях:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
enum MathConstants {
  Pi = 3.14,
  Euler = Math.E,           // Вычисляемое значение
  Golden = (1 + Math.sqrt(5)) / 2  // Формула золотого сечения
}
 
enum Incremental {
  A = 1,
  B = A * 2,      // Используем предыдущее значение
  C = B * 2
}
Важное правило: если в перечислении есть вычисляемое значение, все последующие элементы должны иметь явно заданные значения или тоже быть вычисляемыми. TypeScript не сможет продолжить автоматический отсчёт после вычисляемого значения.

Константные перечисления для оптимизации



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

TypeScript
1
2
3
4
5
6
7
8
const enum Direction {
  Up,
  Down,
  Left,
  Right
}
 
let move = Direction.Up;
После компиляции этот код превратится просто в:

JavaScript
1
let move = 0;
Никаких объектов, никакого лишнего кода — только прямая подстановка значений. Это делает итоговый бандл меньше и быстрее.
Но есть и ограничения: константные перечисления нельзя использовать для динамического доступа к значениям, поскольку самого объекта перечисления в рантайме не существует.

Постоянные и вычисляемые члены перечислений



В контексте TypeScript enum все элементы делятся на постоянные (constant) и вычисляемые (computed). Постоянные значения известны на этапе компиляции, а вычисляемые определяются только во время выполнения:

TypeScript
1
2
3
4
5
6
7
8
9
enum Example {
  // Постоянные члены
  A = 1, 
  B = A * 2,
  
  // Вычисляемые члены  
  C = Math.random(),
  D = Date.now()
}
TypeScript более строг к вычисляемым членам и требует, чтобы после них все последующие члены имели явные инициализаторы.
Перечисления — простой, но мощный инструмент, который делает код более читаемым и безопасным. В следующих разделах мы перейдем к практическому применению перечислений и рассмотрим сценарии, где они особенно полезны.

Лучшие видео ресурсы о программировании, angualr 2, typeScript, react и все сомое вкусное
Всем доброй поры времени. Я нашел новый для себя, и как не странно, вообще новый ресур, с видео уроками по программированию Developer Lessons. ...

Использование Typescript и Google maps API
Привет! Более подходящей темы на форуме не нашел, если промазал, перенесите. пожалуйста. По теме. Делаю функционал по работе с картой на...

Лучшие практики, методы, рекомендации по программированию на Си
Посоветуйте книгу, в которой приводились бы best practice (рекомендации, подходы, лучшие практики) по программированию на языке С. Рекомендации....

[Новичок] Лучшие практики при работе с машиной состояний
Добрый день, Форумчане! Я начинающий инди разработчик. Пытаюсь сделать свою первую игру в стиле 3-Match. Для начала как сейчас я все...


Практическое применение



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

Управление ролями пользователей



Одно из самых частых применений перечислений — система контроля доступа. Вместо работы с разрозненными строками мы можем создать четкую структуру ролей:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum UserRole {
  Admin = "ADMIN",
  User = "USER",
  Guest = "GUEST"
}
 
function checkAccess(role: UserRole) {
  switch (role) {
    case UserRole.Admin:
      return { canCreate: true, canEdit: true, canDelete: true };
    case UserRole.User:
      return { canCreate: true, canEdit: true, canDelete: false };
    case UserRole.Guest:
      return { canCreate: false, canEdit: false, canDelete: false };
    default:
      // TypeScript поможет убедиться, что мы обработали все возможные роли
      const exhaustiveCheck: never = role;
      throw new Error(`Необработанная роль: ${exhaustiveCheck}`);
  }
}
 
// Использование
const permissions = checkAccess(UserRole.User);
console.log(permissions.canDelete); // false
Здесь TypeScript не только помогает избежать опечаток, но и проверяет, что мы обработали все возможные варианты в switch-case. Если позже мы добавим новую роль, но забудем обновить функцию проверки доступа, компилятор выдаст ошибку.

Статусы API и обработка ответов



При работе с REST API перечисления помогают структурировать коды ответов и сделать их обработку более наглядной:

TypeScript
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
enum ApiResponseStatus {
  Success = 200,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  ServerError = 500
}
 
async function fetchData(url: string) {
  try {
    const response = await fetch(url);
    
    if (response.status === ApiResponseStatus.Success) {
      return await response.json();
    } else if (response.status === ApiResponseStatus.Unauthorized) {
      // Перенаправляем на страницу входа
      redirectToLogin();
    } else if (response.status === ApiResponseStatus.ServerError) {
      // Показываем сообщение об ошибке сервера
      showServerErrorNotification();
    }
    // ... другие обработчики
  } catch (error) {
    console.error("Network error:", error);
  }
}
Согласитесь, такой код намного понятнее, чем работа с "голыми" числами. Даже человек, не знакомый с HTTP-кодами, может понять логику обработки ответов.

Машины состояний и жизненные циклы



Представьте, что вы разрабатываете процесс оформления заказа в интернет-магазине. Заказ проходит через несколько этапов, и перечисления идеально подходят для представления этих состояний:

TypeScript
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
31
32
33
34
35
36
37
enum OrderStatus {
  Created = "CREATED",
  PaymentPending = "PAYMENT_PENDING",
  Paid = "PAID",
  Processing = "PROCESSING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED"
}
 
class Order {
  private status: OrderStatus = OrderStatus.Created;
  
  public pay(): void {
    if (this.status === OrderStatus.Created || this.status === OrderStatus.PaymentPending) {
      this.status = OrderStatus.Paid;
      this.notifyUser("Оплата получена");
    } else {
      throw new Error("Невозможно оплатить заказ в текущем статусе");
    }
  }
  
  public ship(): void {
    if (this.status === OrderStatus.Paid || this.status === OrderStatus.Processing) {
      this.status = OrderStatus.Shipped;
      this.notifyUser("Заказ отправлен");
    } else {
      throw new Error("Невозможно отправить заказ в текущем статусе");
    }
  }
  
  // Другие методы управления жизненным циклом
  
  private notifyUser(message: string): void {
    console.log(`Уведомление пользователя: ${message}`);
  }
}
Такой подход делает переходы между состояниями более контролируемыми и предсказуемыми. Это особенно ценно в сложных приложениях, где состояние может меняться в результате различных действий пользователя или системных событий.

Настраиваемые конфигурации



Перечисления могут использоваться для создания настраиваемых конфигураций с ограниченным набором опций:

TypeScript
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
31
32
33
34
35
36
37
38
39
40
41
enum ThemeMode {
  Light = "light",
  Dark = "dark",
  System = "system"
}
 
enum NotificationType {
  Email = "email",
  Push = "push",
  SMS = "sms",
  None = "none"
}
 
class UserPreferences {
  constructor(
    public theme: ThemeMode = ThemeMode.System,
    public notificationMethod: NotificationType = NotificationType.Push
  ) {}
  
  applyTheme(): void {
    switch (this.theme) {
      case ThemeMode.Light:
        document.body.classList.add('light-theme');
        document.body.classList.remove('dark-theme');
        break;
      case ThemeMode.Dark:
        document.body.classList.add('dark-theme');
        document.body.classList.remove('light-theme');
        break;
      case ThemeMode.System:
        const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        document.body.classList.add(prefersDark ? 'dark-theme' : 'light-theme');
        document.body.classList.remove(prefersDark ? 'light-theme' : 'dark-theme');
        break;
    }
  }
}
 
// Использование
const userPrefs = new UserPreferences(ThemeMode.Dark, NotificationType.Email);
userPrefs.applyTheme();
Enum в этом случае не только обеспечивает типобезопасность, но и делает код более самодокументируемым.

Альтернативы enum: union типы и литералы



Несмотря на все преимущества, у enum есть и альтернативы. В некоторых случаях union типы могут быть более подходящим решением:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Вместо enum можно использовать union тип  
type Direction = "up" | "down" | "left" | "right";
 
function move(direction: Direction) {
  switch (direction) {
    case "up": return { x: 0, y: -1 };
    case "down": return { x: 0, y: 1 };
    case "left": return { x: -1, y: 0 };
    case "right": return { x: 1, y: 0 };
  }
}
 
// Использование
const vector = move("up");
Union типы не создают дополнительного кода в скомпилированном JavaScript, что может быть преимуществом для небольших проектов или когда важна оптимизация размера бандла.
Другая альтернатива — использование объекта с модификатором as const для создания набора констант:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Directions = {
  Up: "UP",
  Down: "DOWN", 
  Left: "LEFT",
  Right: "RIGHT"
} as const;
 
type Direction = typeof Directions[keyof typeof Directions];
 
function move(direction: Direction) {
  // ...
}
 
move(Directions.Up); // Работает
move("UP"); // Тоже работает
move("SIDEWAYS"); // Ошибка компиляции
Этот подход сочетает типобезопасность с гибкостью, позволяя использовать как объект констант, так и строковые литералы напрямую.

Enum в React и других фреймворках



В React перечисления особенно полезны для управления состояниями компонентов и обработки событий:

TypeScript
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
31
32
33
34
35
36
37
38
39
40
41
enum LoadingState {
  Initial = "INITIAL",
  Loading = "LOADING",
  Success = "SUCCESS",
  Error = "ERROR"
}
 
const DataFetcher: React.FC = () => {
  const [state, setState] = useState<LoadingState>(LoadingState.Initial);
  const [data, setData] = useState<any>(null);
  const [error, setError] = useState<string | null>(null);
  
  useEffect(() => {
    const fetchData = async () => {
      setState(LoadingState.Loading);
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
        setState(LoadingState.Success);
      } catch (err) {
        setError(err.message);
        setState(LoadingState.Error);
      }
    };
    
    fetchData();
  }, []);
  
  // Рендеринг на основе состояния
  if (state === LoadingState.Initial || state === LoadingState.Loading) {
    return <div>Загрузка...</div>;
  }
  
  if (state === LoadingState.Error) {
    return <div>Ошибка: {error}</div>;
  }
  
  return <div>Данные загружены: {JSON.stringify(data)}</div>;
};
Такой подход делает логику компонента более структурированной и упрощает отладку — вы всегда точно знаете, в каком состоянии находится компонент.

В Angular, Vue и других фреймворках принципы использования enum схожие — они помогают типизировать и структурировать различные аспекты приложения, от маршрутизации до состояний интерфейса.

Проблемы и ограничения



При всех своих преимуществах, перечисления в TypeScript имеют ряд подводных камней, о которых стоит знать заранее. Используя enum без понимания этих нюансов, вы рискуете столкнуться с неочевидными проблемами при разработке и развертывании приложения.

Избыточный JavaScript-код после компиляции



Одна из главных проблем enum — это то, что происходит после компиляции. В отличие от типов и интерфейсов, которые полностью исчезают в JavaScript, перечисления превращаются в реальные объекты. Посмотрите, как выглядит простое перечисление после компиляции:

TypeScript
1
2
3
4
5
6
7
// TypeScript
enum Direction {
  Up,
  Down,
  Left,
  Right
}
После компиляции в JavaScript это превращается в:

JavaScript
1
2
3
4
5
6
7
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
Не самый компактный код, правда? Теперь представьте, что таких перечислений в вашем проекте десятки — это серьезно увеличивает размер итогового бандла.

Исследование, проведенное командой разработчиков из Airbnb, показало, что замена enum на union типы в крупном приложении привела к уменьшению размера бандла примерно на 5-7%. В мобильной разработке это может существенно влиять на скорость загрузки приложения.

Обратное отображение: преимущество или риск?



Мы уже упоминали, что числовые enum поддерживают обратное отображение — можно получить имя по значению. Это удобно для отладки, но создает дополнительный объем кода и может привести к неожиданным проблемам с безопасностью.

TypeScript
1
2
3
4
5
6
7
enum UserRole {
  Admin = 1,
  User = 2,
  Guest = 3
}
 
console.log(UserRole[1]); // Выведет "Admin"
Эта возможность означает, что все имена ролей доступны в рантайме. В приложениях с чувствительными данными это может стать источником утечки информации. Представьте, что злоумышленник сможет перечислить все роли в системе через консоль браузера — не лучший сценарий для безопасности. Строковые перечисления этой проблемы лишены, поскольку не поддерживают обратное отображение, но они все равно создают объект в скомпилированном коде.

Проблемы с числовыми enum и типобезопасность



Неочевидная, но серьезная проблема числовых перечислений — они не так типобезопасны, как кажется. TypeScript позволяет присваивать любое число переменной с типом числового enum:

TypeScript
1
2
3
4
5
6
7
enum Direction {
  Up,
  Down
}
 
// TypeScript это пропустит без ошибок!
let dir: Direction = 10;
Компилятор не выдаст ошибку, хотя значения 10 в перечислении нет. Это может привести к трудноуловимым багам, когда код работает с некорректными значениями. Строковые перечисления такой проблемы не имеют — TypeScript не позволит присвоить произвольную строку переменной со строковым enum типом.

Сериализация и десериализация



При работе с API или localStorage часто возникает необходимость сериализовать и десериализовать данные. И тут с enum могут возникнуть сложности:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum Status {
  Active = "ACTIVE",
  Suspended = "SUSPENDED"
}
 
const user = {
  name: "John",
  status: Status.Active
};
 
// Сериализация - работает нормально
const serialized = JSON.stringify(user);
console.log(serialized); // {"name":"John","status":"ACTIVE"}
 
// Десериализация - теряем связь с enum
const deserialized = JSON.parse(serialized);
console.log(deserialized.status === Status.Active); // true, но это просто строка
 
// А вот проверки не сработают
if (deserialized.status instanceof Status) { // ОШИБКА: Status - не конструктор
  // ...
}
После десериализации мы получаем обычную строку, а не значение перечисления. TypeScript не сможет проверить корректность такого значения на этапе компиляции, и вам придется вручную валидировать данные. Эту проблему можно решить с помощью дополнительной логики:

TypeScript
1
2
3
4
5
6
7
8
9
function isValidStatus(status: string): status is Status {
  return Object.values(Status).includes(status as Status);
}
 
const deserialized = JSON.parse(serialized);
if (isValidStatus(deserialized.status)) {
  // Теперь TypeScript знает, что это валидное значение Status
  const validStatus: Status = deserialized.status;
}
Но это дополнительный код, который нужно писать и поддерживать.

Проблемы в крупных проектах с несколькими разработчиками



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

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
// Разработчик A добавляет:
enum UserStatus {
  Active,
  Inactive,
  Pending
}
 
// Разработчик B в другой ветке добавляет:
enum UserStatus {
  Active,
  Inactive,
  Blocked
}
После слияния кода значения могут перемешаться, и смысл чисел изменится, что приведет к неправильной работе существующего кода. Строковые перечисления минимизируют эту проблему.

Проблемы в React и Angular приложениях



В React, особенно при использовании Redux, перечисления часто используются для типов действий. Однако это может привести к проблемам с мемоизацией:

TypeScript
1
2
3
4
5
6
7
8
9
enum ActionType {
  Increment = "INCREMENT",
  Decrement = "DECREMENT"
}
 
// Хук useCallback может не работать как ожидается
const handleIncrement = useCallback(() => {
  dispatch({ type: ActionType.Increment });
}, [ActionType.Increment]); // ActionType.Increment - объект, а не примитив
Поскольку значения enum - это объекты, зависимости в хуках React могут работать не так, как ожидается.

В Angular похожие проблемы возникают с ChangeDetectionStrategy.OnPush, которая полагается на изменение ссылок на объекты для обнаружения изменений.

Производительность при частых проверках



В высоконагруженных приложениях, где проверки значений enum выполняются часто (например, в игровых движках или при обработке больших объемов данных), это может создать небольшую накладку на производительность по сравнению с прямыми сравнениями примитивов.

TypeScript
1
2
3
4
5
// Менее эффективно (обращение к объекту)
if (status === UserStatus.Active) { ... }
 
// Более эффективно (сравнение примитивов)
if (status === "ACTIVE") { ... }
Разница обычно незначительна, но в критичных к производительности местах может иметь значение.

Ограничения Tree Shaking



Современные бандлеры, такие как Webpack и Rollup, используют технику Tree Shaking для удаления неиспользуемого кода. Однако с enum эта оптимизация может работать не так эффективно, как хотелось бы.

TypeScript
1
2
3
4
5
6
7
8
enum Features {
  Basic = "BASIC",
  Pro = "PRO",
  Enterprise = "ENTERPRISE"
}
 
// Если используется только одно значение
const userFeature = Features.Basic;
Даже если в коде используется только одно значение из перечисления, весь объект Features попадет в итоговый бандл. С константными объектами или union типами Tree Shaking работает лучше. Все эти проблемы не означают, что от enum нужно полностью отказаться. Просто важно понимать ограничения и выбирать правильный инструмент для конкретной задачи. В следующем разделе мы обсудим лучшие практики, которые помогут вам использовать перечисления максимально эффективно и избегать описанных проблем.

Лучшие практики



Теперь, когда мы рассмотрели преимущества и недостатки перечислений, давайте сосредоточимся на практических рекомендациях. Как использовать enum в TypeScript так, чтобы получить максимум пользы и минимум проблем?

Предпочитайте строковые перечисления числовым



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

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
// Хороший подход
enum PaymentMethod {
  CreditCard = "CREDIT_CARD",
  PayPal = "PAYPAL",
  BankTransfer = "BANK_TRANSFER"
}
 
// Избегайте, если нет особых причин
enum PaymentMethod {
  CreditCard, // 0
  PayPal,     // 1
  BankTransfer // 2
}
Строковые перечисления:
  • Не позволяют присваивать произвольные строки (в отличие от числовых, которые принимают любые числа).
  • Более понятны при отладке, так как показывают осмысленные строки.
  • Не создают обратного отображения, что уменьшает размер скомпилированного кода.

Кроме того, строковые значения часто пригодятся при сериализации, работе с API или при сохранении в базе данных.

Используйте const enum для оптимизации



Если вам не нужно обращаться к объекту перечисления в рантайме, а только к его значениям, используйте const enum. Это значительно уменьшает размер скомпилированного кода:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Обычное перечисление создает объект
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}
 
// const enum подставляет значения напрямую
const enum OptimizedColor {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}
 
// В JavaScript превратится в:
// let normalColor = Color.Red;    // let normalColor = Color.Red;
// let fastColor = OptimizedColor.Red; // let fastColor = "RED";
let normalColor = Color.Red;
let fastColor = OptimizedColor.Red;
Обратите внимание: const enum имеет ограничения. Вы не сможете:
  • Обращаться к перечислению динамически через квадратные скобки (например, OptimizedColor['Red']).
  • Перебирать значения перечисления с помощью Object.keys или Object.values.
  • Использовать значения в некоторых сценариях, требующих доступа к runtime-объекту.

Кроме того, const enum может конфликтовать с некоторыми настройками компилятора, особенно с опцией --isolatedModules или при использовании Babel.

Избегайте гетерогенных перечислений



Смешивание строк и чисел в одном перечислении создает неопределенность и усложняет понимание кода:

TypeScript
1
2
3
4
5
6
7
// Избегайте этого
enum Mixed {
  Zero = 0,
  One = 1,
  Yes = "YES",
  No = "NO"
}
Такой код трудно поддерживать, и он может приводить к неочевидным ошибкам. Лучше разделить перечисление на два отдельных, каждое со своим типом:

TypeScript
1
2
3
4
5
6
7
8
9
10
// Лучше так
enum Numbers {
  Zero = 0,
  One = 1
}
 
enum Answers {
  Yes = "YES",
  No = "NO"
}

Используйте union типы для небольших наборов значений



Если вам нужен небольшой, фиксированный набор значений, и вы заботитесь о размере бандла, рассмотрите использование union типов вместо enum:

TypeScript
1
2
3
4
5
6
7
8
9
// Легкая альтернатива для простых случаев
type CardinalDirection = "north" | "south" | "east" | "west";
 
function move(direction: CardinalDirection) {
  // ...
}
 
move("north"); // OK
move("northwest"); // Ошибка компиляции
Union типы не создают дополнительный код в JavaScript и обеспечивают ту же типобезопасность, что и строковые перечисления. Они особенно полезны, когда:
  • Набор значений небольшой и стабильный.
  • Вам не нужно обращаться к именам значений программно.
  • Важна оптимизация размера бандла.

Используйте объекты с as const для более гибкого подхода



Если вам нужна большая гибкость, но вы хотите избежать недостатков enum, рассмотрите объекты с модификатором as const:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Гибкая альтернатива с хорошей производительностью
const HttpStatus = {
  OK: 200,
  Created: 201,
  BadRequest: 400,
  NotFound: 404,
  ServerError: 500
} as const;
 
// Создаем тип из значений объекта
type HttpStatusCode = typeof HttpStatus[keyof typeof HttpStatus];
 
function handleResponse(status: HttpStatusCode) {
  if (status === HttpStatus.OK) {
    // ...
  }
}
Этот подход:
  • Не создает дополнительных IIFE в скомпилированном коде.
  • Позволяет использовать как объект (HttpStatus.OK), так и прямые значения (200).
  • Хорошо работает с Tree Shaking.
  • Легко расширяется без изменения существующего кода.

Не используйте перечисления для констант, которые часто изменяются



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

У меня был проект, где мы использовали enum для статусов заказа. Когда бизнес начал быстро добавлять новые статусы, мы постоянно сталкивались с конфликтами и рефакторингом кода. В итоге мы перешли на динамическую систему статусов, хранящуюся в базе данных, и использовали типизированные строки только для программного интерфейса.

Исчерпывающие проверки с помощью never



Одна из сильных сторон TypeScript — возможность убедиться, что вы обработали все возможные значения enum. Используйте это с помощью типа never:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum TaskStatus {
  ToDo = "TODO",
  InProgress = "IN_PROGRESS",
  OnHold = "ON_HOLD",
  Done = "DONE"
}
 
function getNextStatus(current: TaskStatus): TaskStatus {
  switch (current) {
    case TaskStatus.ToDo:
      return TaskStatus.InProgress;
    case TaskStatus.InProgress:
      return TaskStatus.Done;
    case TaskStatus.OnHold:
      return TaskStatus.InProgress;
    case TaskStatus.Done:
      return TaskStatus.Done;
    default:
      // Если добавят новый статус и забудут обновить эту функцию,
      // TypeScript выдаст ошибку
      const exhaustiveCheck: never = current;
      throw new Error(`Необработанный статус: ${exhaustiveCheck}`);
  }
}
Этот прием особенно полезен при рефакторинге: если вы добавите новое значение в перечисление, TypeScript укажет все места, где нужно обновить логику.

Используйте namespace enum для группировки связанных перечислений



Если у вас много связанных перечислений, используйте namespace для их организации:

TypeScript
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
namespace AppConstants {
  export enum Theme {
    Light = "LIGHT",
    Dark = "DARK",
    System = "SYSTEM"
  }
  
  export enum Language {
    English = "en",
    Spanish = "es",
    French = "fr",
    German = "de"
  }
  
  export enum Currency {
    USD = "USD",
    EUR = "EUR",
    GBP = "GBP"
  }
}
 
// Использование
const userPrefs = {
  theme: AppConstants.Theme.Dark,
  language: AppConstants.Language.English
};
Этот подход улучшает организацию кода и снижает вероятность коллизий имен.

Создавайте вспомогательные функции для работы с перечислениями



Вместо того чтобы разбрасывать логику работы с перечислениями по всему коду, создавайте вспомогательные функции:

TypeScript
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
enum PermissionLevel {
  None = 0,
  Read = 1,
  Write = 2,
  Admin = 3
}
 
// Вспомогательные функции для проверки прав
function hasReadAccess(level: PermissionLevel): boolean {
  return level >= PermissionLevel.Read;
}
 
function hasWriteAccess(level: PermissionLevel): boolean {
  return level >= PermissionLevel.Write;
}
 
function isAdmin(level: PermissionLevel): boolean {
  return level === PermissionLevel.Admin;
}
 
// Использование
function editDocument(user: User, document: Document) {
  if (!hasWriteAccess(user.permissionLevel)) {
    throw new Error("У вас нет прав на редактирование");
  }
  
  // Логика редактирования...
}
Такой подход улучшает читаемость кода и упрощает внесение изменений в будущем.

Документируйте перечисления



Хорошая документация особенно важна для enum, поскольку они часто представляют бизнес-концепции, которые должны быть понятны всем участникам разработки:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Статусы выполнения задачи в системе управления проектами.
 * 
 * @remarks
 * Статус BLOCKED не отображается в UI для обычных пользователей,
 * но используется для аналитики и системных операций.
 */
enum TaskStatus {
  /** Задача создана, но работа еще не началась */
  New = "NEW",
  
  /** Задача в процессе выполнения */
  InProgress = "IN_PROGRESS",
  
  /** Задача приостановлена из-за внешних зависимостей */
  Blocked = "BLOCKED",
  
  /** Работа над задачей завершена */
  Completed = "COMPLETED"
}
Хорошая документация сделает ваш код более понятным и уменьшит количество вопросов от других разработчиков.

Валидация значений, полученных из внешних источников



Когда вы получаете данные из API или пользовательского ввода, всегда проверяйте, соответствуют ли они вашим перечислениям:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum ProductCategory {
  Electronics = "ELECTRONICS",
  Clothing = "CLOTHING",
  Books = "BOOKS",
  HomeGoods = "HOME_GOODS"
}
 
// Проверка значения из API
function isValidCategory(category: string): category is ProductCategory {
  return Object.values(ProductCategory).includes(category as ProductCategory);
}
 
// Использование
async function fetchProducts(categoryFromUrl: string) {
  if (!isValidCategory(categoryFromUrl)) {
    throw new Error(`Недопустимая категория: ${categoryFromUrl}`);
  }
  
  const category: ProductCategory = categoryFromUrl; // Теперь TypeScript знает, что это валидная категория
  const response = await api.getProducts({ category });
  // ...
}
Эта техника помогает сохранить типобезопасность при взаимодействии с нетипизированными источниками данных.

Переиспользуйте значения enum для динамических операций



Иногда нужно программно работать со всеми значениями перечисления. Для этого используйте Object.values или Object.keys:

TypeScript
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 FilterOption {
  ShowAll = "SHOW_ALL",
  Active = "ACTIVE",
  Completed = "COMPLETED",
  Deleted = "DELETED"
}
 
// Создаем кнопки фильтрации для каждого варианта
function generateFilterButtons() {
  return Object.values(FilterOption).map(option => {
    const button = document.createElement('button');
    button.textContent = option;
    button.addEventListener('click', () => applyFilter(option));
    return button;
  });
}
 
function applyFilter(filter: FilterOption) {
  // Логика фильтрации...
}
 
// Получаем все кнопки
const filterButtons = generateFilterButtons();
// Добавляем их на страницу
filterButtons.forEach(button => document.getElementById('filters').appendChild(button));
Этот подход позволяет динамически создавать интерфейсы, основанные на значениях enum, и автоматически обновлять их при изменении перечисления.

Резюме лучших практик



Выбор подхода к использованию enum зависит от конкретного сценария и приоритетов вашего проекта. Вот краткое резюме:
1. Для типобезопасности и читабельности: Используйте строковые enum.
2. Для оптимизации бандла: Выбирайте const enum или union типы.
3. Для гибкости и сохранения производительности: Применяйте объекты с as const.
4. Для сложных взаимосвязанных перечислений: Организуйте их в пространства имен (namespace).
5. Для критически важного кода: Добавляйте исчерпывающие проверки с типом never.
6. При работе с внешними данными: Всегда валидируйте значения перед использованием.
7. Для поддержки: Документируйте перечисления и создавайте вспомогательные функции.

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

В моей практике наиболее удачным оказался гибридный подход: использование строковых enum для ключевых бизнес-концепций, const enum для внутренних констант и union типы для простых случаев. Такой микс позволяет балансировать между читабельностью, типобезопасностью и производительностью.

Учить новичку JavaScript и Typescript или достаточно одного Typescript?
Добрый день. Изучаю Asp net core. Хочу дополнительно изучить JavaScript и Typescript. Встал вопрос: есть ли смысл учить JavaScript, если сейчас...

Использование перечисления в запросе
Есть вот такое перечисление и функция: Enum StringTypeMsg BP = 1 ' бизнес-процесс Task = 2 ' задача ...

Использование перечисления для выбора варианта
Здравствуйте. Решил вот сесть за изучение с++, мой первый яп. Мне всегда было трудно с логикой и математикой и первые же упражнения, встречающиеся...

Использование перечисления для поля структуры
Всем снова здравствуйте. Сново проблема) Дано задание: Личная библиотека. Картотека домашней библиотеки: выходные данные книги (авторы, название,...

Заполнение дневника практики и отзыва руководителя практики от предприятия
Всем привет. Нужна помощь. У меня фиктивная практика. Т.е. оффициально она есть, но её как бы и нет. Завтра организация сдаёт печать - поэтому мне...

Ошибка C2665, определение и использование перечисления C++/CLI
Здравствуйте! Меня с ветки C++ для начинающих отправили сюда. Не знал что C++ бывает разным. Объясните пожалуйста. Уже 2 часа сижу и пытаюсь понять...

Использование перечисления enum с пользовательсим типом, а не с базовыми int, byte, short, long
Всем привет! Создаю консольную игру-бродилку, по одному из заданий известной софтверной фирмы, которая должна помочь разобраться с принципами ООП....

Использование имени перечисления (типа данных C++) для добавления к строковой переменной в цикле
Хотелось бы в цикле использовать значение перечисления (типа C++), например:enum spectrum { red, orange, yellow, green, blue, violet };а затем в этом...

Перевод C# на TypeScript
Доброго времени суток))) (Извините если не в ту тему) Существует рабочая программы для локального пользования(C# WinForm). Делаю Web Asp.net версию...

TypeScript vs Script# vs
У кого какой опыт ? - сравнительные достоинства и недостатки.

Typescript instanceof
Здравствуйте! Подскажите, пожалуйста, почему данный код выводит false? export default class ApiError extends Error { status; ...

TypeScript, ошибка
У меня проблема. Прописываю я код, в консоли мне не показывает ни одной ошибки. После написания кода я прописываю команды: tsc file.ts и node file.ts...

Метки angular, react, typescript, vue
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Обнаружение объектов в реальном времени на Python с YOLO и OpenCV
AI_Generated 29.04.2025
Компьютерное зрение — одна из самых динамично развивающихся областей искусственного интеллекта. В нашем мире, где визуальная информация стала доминирующим способом коммуникации, способность машин. . .
Эффективные парсеры и токенизаторы строк на C#
UnmanagedCoder 29.04.2025
Обработка текстовых данных — частая задача в программировании, с которой сталкивается почти каждый разработчик. Парсеры и токенизаторы составляют основу множества современных приложений: от. . .
C++ в XXI веке - Эволюция языка и взгляд Бьярне Страуструпа
bytestream 29.04.2025
C++ существует уже более 45 лет с момента его первоначальной концепции. Как и было задумано, он эволюционировал, отвечая на новые вызовы, но многие разработчики продолжают использовать C++ так, будто. . .
Слабые указатели в Go: управление памятью и предотвращение утечек ресурсов
golander 29.04.2025
Управление памятью — один из краеугольных камней разработки высоконагруженных приложений. Го (Go) занимает уникальную нишу в этом вопросе, предоставляя разработчикам автоматическое управление памятью. . .
Разработка кастомных расширений для компилятора C++
NullReferenced 29.04.2025
Создание кастомных расширений для компиляторов C++ — инструмент оптимизации кода, внедрения новых языковых функций и автоматизации задач. Многие разработчики недооценивают гибкость современных. . .
Гайд по обработке исключений в C#
stackOverflow 29.04.2025
Разработка надёжного программного обеспечения невозможна без грамотной обработки исключительных ситуаций. Любая программа, независимо от её размера и сложности, может столкнуться с непредвиденными. . .
Создаем RESTful API с Laravel
Jason-Webb 28.04.2025
REST (Representational State Transfer) — это архитектурный стиль, который определяет набор принципов для создания веб-сервисов. Этот подход к построению API стал стандартом де-факто в современной. . .
Дженерики в C# - продвинутые техники
stackOverflow 28.04.2025
История дженериков началась с простой идеи — создать механизм для разработки типобезопасного кода без потери производительности. До их появления программисты использовали неуклюжие преобразования. . .
Тестирование в Python: PyTest, Mock и лучшие практики TDD
py-thonny 28.04.2025
Тестирование кода играет весомую роль в жизненном цикле разработки программного обеспечения. Для разработчиков Python существует богатый выбор инструментов, позволяющих создавать надёжные и. . .
Работа с PDF в Java с iText
Javaican 28.04.2025
Среди всех форматов PDF (Portable Document Format) заслуженно занимает особое место. Этот формат, созданный компанией Adobe, превратился в универсальный стандарт для обмена документами, не зависящий. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru