Avatar ·

Как получить список имен (ключей) всех значений перечисления (enum) в TypeScript?

Всем привет! Я только начал изучать TypeScript и столкнулся с проблемой при работе с перечислениями (enum). У меня есть вот такое перечисление для статусов заказа: ```typescript enum OrderStatus { Pending = "PENDING", Processing = "PROCESSING", Shipped = "SHIPPED", Delivered = "DELIVERED", Cancelled = "CANCELLED" } ``` Мне нужно в интерфейсе администратора вывести список всех статусов в выпадающем списке (select). Я попробовал использовать `Object.keys()` и `Object.values()`, но получил странные результаты. Когда я пишу: ```typescript console.log(Object.keys(OrderStatus)); ``` В консоли выводится: `["1", "2", "3", "4", "5", "Pending", "Processing", "Shipped", "Delivered", "Cancelled"]` Почему там появляются цифры? Я ожидал увидеть только имена: `["Pending", "Processing", "Shipped", "Delivered", "Cancelled"]`. А при попытке получить значения через `Object.values()` тоже получается каша. Я читал, что это как-то связано с тем, как TypeScript компилирует enum в JavaScript, но не могу разобраться. Подскажите, как правильно получить только имена (строковые ключи) enum? И есть ли разница между числовыми и строковыми enum в этом плане? Заранее спасибо! <<>>

Проблема: как работает enum в JavaScript

Вы абсолютно правы — странное поведение связано с тем, как TypeScript компилирует enum в JavaScript. Давайте разберемся на примере вашего кода.

Когда вы создаете enum со строковыми значениями, TypeScript генерирует объект с двунаправленным отображением (reverse mapping). Это сделано для обратной совместимости с числовыми enum. Ваш OrderStatus после компиляции превращается примерно в такой JavaScript-код:

var OrderStatus;
(function (OrderStatus) {
    OrderStatus["Pending"] = "PENDING";
    OrderStatus["Processing"] = "PROCESSING";
    OrderStatus["Shipped"] = "SHIPPED";
    OrderStatus["Delivered"] = "DELIVERED";
    OrderStatus["Cancelled"] = "CANCELLED";
    // Обратное отображение (для числовых enum):
    OrderStatus[OrderStatus["Pending"] = "PENDING"] = "Pending";
    // ... и так далее
})(OrderStatus || (OrderStatus = {}));

На самом деле для строковых enum обратное отображение не создается, но цифры появляются из-за другого механизма. В реальности для строковых enum TypeScript генерирует только прямые отображения. Однако Object.keys() все равно может показывать неожиданные результаты, если вы используете смешанные или числовые enum.

Правильное решение: фильтрация ключей

Самый надежный способ получить только имена (строковые ключи) enum — использовать Object.keys() с фильтрацией по типу значения или просто игнорировать числовые ключи. Вот рабочий пример:

enum OrderStatus {
  Pending = "PENDING",
  Processing = "PROCESSING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED"
}

// Способ 1: для строковых enum (рекомендуется)
function getEnumNames<T extends Record<string, string>>(enumObj: T): string[] {
  return Object.keys(enumObj).filter(key => isNaN(Number(key)));
}

console.log(getEnumNames(OrderStatus));
// Вывод: ["Pending", "Processing", "Shipped", "Delivered", "Cancelled"]

Универсальное решение для любых enum

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

function getEnumEntries<T extends Record<string, string | number>>(enumObj: T): { key: string; value: T[keyof T] }[] {
  const keys = Object.keys(enumObj).filter(key => isNaN(Number(key)));
  return keys.map(key => ({ key, value: enumObj[key] }));
}

// Для строкового enum:
console.log(getEnumEntries(OrderStatus));
// [
//   { key: "Pending", value: "PENDING" },
//   { key: "Processing", value: "PROCESSING" },
//   ...
// ]

// Для числового enum:
enum NumericStatus {
  Active = 1,
  Inactive = 2,
  Archived = 3
}

console.log(getEnumEntries(NumericStatus));
// [
//   { key: "Active", value: 1 },
//   { key: "Inactive", value: 2 },
//   { key: "Archived", value: 3 }
// ]

Важные моменты

  • Числовые enum создают обратное отображение (ключ-значение и значение-ключ), поэтому Object.keys() будет содержать и числа, и строки.
  • Строковые enum не имеют обратного отображения, поэтому Object.keys() вернет только строковые ключи. Но фильтрация все равно не помешает для универсальности.
  • Если вам нужны только значения (value) enum, используйте Object.values() с аналогичной фильтрацией.
  • В TypeScript 5.0+ появились улучшения для работы с enum, но базовая логика получения имен осталась прежней.

Для вашего выпадающего списка рекомендую использовать первый способ с getEnumNames(). Это даст вам чистый массив имен для отображения в UI.

<<>>

Альтернативный подход: использование const enum и Record

Если вы контролируете код и хотите избежать проблем с обратным отображением, можно использовать const enum (константное перечисление) или вообще отказаться от enum в пользу обычных объектов.

Вариант с const enum

const enum OrderStatus {
  Pending = "PENDING",
  Processing = "PROCESSING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED"
}

// Для const enum нельзя получить ключи через Object.keys(),
// так как он не создает объект в рантайме.
// Нужно использовать вспомогательный объект:
const OrderStatusNames: Record<string, string> = {
  Pending: "PENDING",
  Processing: "PROCESSING",
  Shipped: "SHIPPED",
  Delivered: "DELIVERED",
  Cancelled: "CANCELLED"
};

console.log(Object.keys(OrderStatusNames));
// ["Pending", "Processing", "Shipped", "Delivered", "Cancelled"]

Вариант с обычным объектом (as const)

const OrderStatus = {
  Pending: "PENDING",
  Processing: "PROCESSING",
  Shipped: "SHIPPED",
  Delivered: "DELIVERED",
  Cancelled: "CANCELLED"
} as const;

type OrderStatus = typeof OrderStatus[keyof typeof OrderStatus];

// Получаем имена:
console.log(Object.keys(OrderStatus));
// ["Pending", "Processing", "Shipped", "Delivered", "Cancelled"]

// Получаем значения:
console.log(Object.values(OrderStatus));
// ["PENDING", "PROCESSING", "SHIPPED", "DELIVERED", "CANCELLED"]

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