Как перебрать enum для radio-кнопок в TypeScript: туториал

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему enum и radio-кнопки — идеальная пара?

Представьте: вы пишете форму на TypeScript. Нужно дать пользователю выбрать один вариант из нескольких — например, уровень сложности, статус заказа или пол. Где хранить эти варианты? Можно захардкодить массив строк, но это путь в никуда. Когда вариантов станет много, вы запутаетесь, а если поменяется бизнес-логика — придётся править код в десяти местах.

Тут на сцену выходят enum (перечисления). Они позволяют задать набор именованных констант, которые и читаются человеком, и понимаются машиной. А radio-кнопки — классический UI-элемент для выбора одного значения из списка. Соединить их — значит получить чистый, поддерживаемый код. Звучит круто, но есть нюанс: как именно перебрать enum, чтобы отобразить его в шаблоне? Именно этот вопрос мучает тысячи разработчиков на Stack Overflow. Давайте разберёмся раз и навсегда.

Проблема: enum — это не массив

В TypeScript enum — это не просто список строк. В зависимости от того, как вы его объявили (числовой или строковый), он ведёт себя по-разному. Числовой enum создаёт обратную связь: ключ -> значение и значение -> ключ. Строковый — только прямую. Поэтому просто взять и написать for (let item in MyEnum) — плохая идея. Вы получите не только нужные ключи, но и мусорные обратные отображения.

Вот как выглядит типичная ловушка:

enum Status {
  Active = 1,
  Inactive = 2,
  Pending = 3
}

// Так делать НЕ надо
for (let key in Status) {
  console.log(key); // "1", "2", "3", "Active", "Inactive", "Pending"
}

Видите? В консоль попали и названия, и значения. Если вы попытаетесь вывести это в radio-кнопки, пользователь увидит непонятные цифры. Нам это не подходит.

Решение №1: Object.values() для строковых enum

Самый простой и надёжный способ — использовать Object.values(). Этот метод возвращает массив значений перечисляемых свойств объекта. Для строковых enum он работает идеально, потому что у них нет обратного маппинга (почти всегда).

Пример: статус заказа

enum OrderStatus {
  New = 'new',
  Processing = 'processing',
  Shipped = 'shipped',
  Delivered = 'delivered',
  Cancelled = 'cancelled'
}

// Получаем массив значений
const statusValues = Object.values(OrderStatus);
// ['new', 'processing', 'shipped', 'delivered', 'cancelled']

// Теперь можно легко отобразить в шаблоне (React)
function StatusSelector() {
  return (
    <div>
      {statusValues.map((status) => (
        <label key={status}>
          <input type="radio" name="status" value={status} />
          {status}
        </label>
      ))}
    </div>
  );
}

Всё гениально просто. Мы получили массив строк и просто проитерировали его. Подходит для React, Angular, Vue — любого фреймворка.

Решение №2: Object.keys() + фильтрация для числовых enum

С числовыми enum ситуация хитрее. Object.values() вернёт не только строковые ключи, но и числовые значения, потому что JavaScript считает их «свойствами» объекта. Нам нужно отфильтровать только строки (ключи).

Пример: уровень доступа

enum AccessLevel {
  Guest = 0,
  User = 1,
  Moderator = 2,
  Admin = 3
}

// Фильтруем: оставляем только строки (ключи)
const levelKeys = Object.keys(AccessLevel).filter(key => isNaN(Number(key)));
// ['Guest', 'User', 'Moderator', 'Admin']

// Если нужно получить массив объектов для удобного отображения
const levelOptions = levelKeys.map(key => ({
  label: key,
  value: AccessLevel[key as keyof typeof AccessLevel]
}));
// [
//   { label: 'Guest', value: 0 },
//   { label: 'User', value: 1 },
//   ...
// ]

Теперь у вас есть массив опций, где label — человекочитаемое имя, а value — числовой код, который удобно отправлять на сервер.

Решение №3: Кастомный хелпер (универсальный)

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

// enum-utils.ts
type EnumType = Record<string, string | number>;

export function enumToOptions<T extends EnumType>(enumObj: T): { label: string; value: T[keyof T] }[] {
  return (Object.keys(enumObj) as Array<keyof T>)
    .filter(key => isNaN(Number(key))) // отбрасываем числовые ключи
    .map(key => ({
      label: String(key),
      value: enumObj[key]
    }));
}

// Использование
enum Color {
  Red = '#FF0000',
  Green = '#00FF00',
  Blue = '#0000FF'
}

const colorOptions = enumToOptions(Color);
// [
//   { label: 'Red', value: '#FF0000' },
//   { label: 'Green', value: '#00FF00' },
//   { label: 'Blue', value: '#0000FF' }
// ]

Этот хелпер работает и с числовыми, и со строковыми enum. Добавьте его в утилиты и забудьте о проблеме навсегда.

Решение №4: Использование Record и Map (продвинутый уровень)

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

enum TaskPriority {
  Low = 'low',
  Medium = 'medium',
  High = 'high',
  Critical = 'critical'
}

// Дополнительная информация для каждого приоритета
const priorityMeta: Record<TaskPriority, { label: string; color: string }> = {
  [TaskPriority.Low]: { label: 'Низкий', color: 'green' },
  [TaskPriority.Medium]: { label: 'Средний', color: 'orange' },
  [TaskPriority.High]: { label: 'Высокий', color: 'red' },
  [TaskPriority.Critical]: { label: 'Критический', color: 'darkred' }
};

// Теперь получаем массив для radio-кнопок
const priorityOptions = (Object.values(TaskPriority) as TaskPriority[]).map(value => ({
  value,
  ...priorityMeta[value]
}));
// [
//   { value: 'low', label: 'Низкий', color: 'green' },
//   { value: 'medium', label: 'Средний', color: 'orange' },
//   ...
// ]

// В шаблоне (React):
function PrioritySelector() {
  return (
    <div>
      {priorityOptions.map(({ value, label, color }) => (
        <label key={value} style={{ color }}>
          <input type="radio" name="priority" value={value} />
          {label}
        </label>
      ))}
    </div>
  );
}

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

Блоги

Рекомендаций по книгам