Как перебрать значения enum для отображения в radio button в TypeScript
Правильный способ итерации по значениям 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 — технические идентификаторы, а не человекочитаемые строки.