Avatar ·

Как перебрать значения enum для отображения в radio button в TypeScript

Привет! Я только начал изучать TypeScript и столкнулся с проблемой при работе с enum. У меня есть перечисление для статусов заказа: ```typescript enum OrderStatus { Pending = 'pending', Processing = 'processing', Shipped = 'shipped', Delivered = 'delivered', Cancelled = 'cancelled' } ``` Мне нужно отобразить эти статусы в виде набора radio button на форме. Я хочу, чтобы пользователь мог выбрать один из вариантов. Проблема в том, что я не понимаю, как правильно получить все значения enum в виде массива строк, чтобы потом использовать их в цикле для создания radio button. Я пробовал писать так: ```typescript const statuses = Object.keys(OrderStatus); ``` Но это возвращает ключи, а не значения. Если я использую `Object.values()`, то получаю массив, но там почему-то есть и ключи, и значения. Как правильно? Я хочу получить массив: `['pending', 'processing', 'shipped', 'delivered', 'cancelled']`, чтобы потом в шаблоне сделать что-то вроде: ```typescript statuses.forEach(status => { // создать radio button с value = status }); ``` Подскажите, как это сделать правильно? Желательно с учётом того, что enum может быть строковым или числовым. Заранее спасибо! <<>>

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

Ваша проблема типична для начинающих разработчиков на TypeScript. Дело в том, что поведение Object.keys() и Object.values() зависит от типа enum — числовой он или строковый. Но есть универсальное решение.

1. Для строковых enum (ваш случай)

Строковые enum — самые простые. Они не создают обратной маппинга (ключ → значение и значение → ключ), поэтому Object.values() работает корректно:

enum OrderStatus {
  Pending = 'pending',
  Processing = 'processing',
  Shipped = 'shipped',
  Delivered = 'delivered',
  Cancelled = 'cancelled'
}

// ✅ Правильно для строковых enum
const statusValues: string[] = Object.values(OrderStatus);
// Результат: ['pending', 'processing', 'shipped', 'delivered', 'cancelled']

// Использование в цикле
statusValues.forEach(status => {
  console.log(`<input type="radio" value="${status}" />`);
});

2. Для числовых enum — проблема и решение

Числовые enum создают обратную маппинг (ключ → значение и значение → ключ). Поэтому Object.values() вернёт и строки, и числа:

enum NumericStatus {
  Pending = 0,
  Processing = 1,
  Shipped = 2
}

console.log(Object.values(NumericStatus));
// ❌ ['Pending', 'Processing', 'Shipped', 0, 1, 2] — дублирование!

Чтобы получить только значения (числа), используйте фильтрацию:

const numericValues = Object.values(NumericStatus)
  .filter(value => typeof value === 'number');
// Результат: [0, 1, 2]

3. Универсальное решение для любого enum

Лучший способ — написать вспомогательную функцию:

function getEnumValues<T extends Record<string, string | number>>(enumObj: T): T[keyof T][] {
  return Object.values(enumObj).filter(
    (value): value is T[keyof T] => typeof value === 'string' || typeof value === 'number'
  );
}

// Использование:
const statuses = getEnumValues(OrderStatus);
// ['pending', 'processing', 'shipped', 'delivered', 'cancelled']

const numericStatuses = getEnumValues(NumericStatus);
// [0, 1, 2]

4. Пример с radio button в React

Вот полный рабочий пример для React (но логика подходит для любого фреймворка):

import React, { useState } from 'react';

enum OrderStatus {
  Pending = 'pending',
  Processing = 'processing',
  Shipped = 'shipped',
  Delivered = 'delivered',
  Cancelled = 'cancelled'
}

function StatusSelector() {
  const [selectedStatus, setSelectedStatus] = useState<string>('');

  const statusValues = Object.values(OrderStatus);

  return (
    <div>
      <h3>Выберите статус:</h3>
      {statusValues.map(status => (
        <label key={status} style={{ display: 'block', margin: '8px 0' }}>
          <input
            type="radio"
            name="orderStatus"
            value={status}
            checked={selectedStatus === status}
            onChange={(e) => setSelectedStatus(e.target.value)}
          />
          {status.charAt(0).toUpperCase() + status.slice(1)}
        </label>
      ))}
      <p>Выбрано: {selectedStatus}</p>
    </div>
  );
}

5. Важные советы

  • Всегда проверяйте тип enum — строковые enum проще, числовые требуют фильтрации.
  • Используйте дженерики для создания переиспользуемых функций.
  • Для отображения часто нужно преобразовать значения (например, сделать первую букву заглавной).
  • Если enum большой — вынесите его в отдельный файл и экспортируйте.

Теперь вы знаете, как правильно работать с enum в TypeScript при создании форм и других UI-элементов. Удачи в разработке!

<<>>

Альтернативный подход: создание массива с метками и значениями

Если вам нужно не только перебрать значения, но и отобразить читаемые метки для radio button, рекомендую создать вспомогательный массив объектов:

enum OrderStatus {
  Pending = 'pending',
  Processing = 'processing',
  Shipped = 'shipped',
  Delivered = 'delivered',
  Cancelled = 'cancelled'
}

// Маппинг значений к читаемым меткам
const statusOptions: { label: string; value: string }[] = [
  { label: 'Ожидание', value: OrderStatus.Pending },
  { label: 'В обработке', value: OrderStatus.Processing },
  { label: 'Отправлен', value: OrderStatus.Shipped },
  { label: 'Доставлен', value: OrderStatus.Delivered },
  { label: 'Отменён', value: OrderStatus.Cancelled }
];

// Использование в шаблоне
statusOptions.forEach(option => {
  console.log(`<input type="radio" value="${option.value}" /> ${option.label}`);
});

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

Для автоматизации можно написать функцию, которая генерирует такой массив из enum и словаря меток:

function createEnumOptions<T extends string>(
  enumObj: Record<string, T>,
  labels: Record<T, string>
): { label: string; value: T }[] {
  return Object.values(enumObj).map(value => ({
    label: labels[value] || value,
    value
  }));
}

const options = createEnumOptions(OrderStatus, {
  pending: 'Ожидание',
  processing: 'В обработке',
  shipped: 'Отправлен',
  delivered: 'Доставлен',
  cancelled: 'Отменён'
});

Этот метод особенно полезен для интернационализации (i18n) и когда значения enum — технические идентификаторы, а не человекочитаемые строки.