Как получить список имен (ключей) всех значений перечисления (enum) в TypeScript?
Проблема: как работает 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 (например, автогенерация числовых значений).
Ответов пока нет.
Войдите чтобы оставить ответ