Как получить имена enum в TypeScript: магия под капотом

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

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

Начать курс

Вступление: Вечный вопрос на Stack Overflow

Вы когда-нибудь задумывались, как опытные разработчики на TypeScript получают имена значений enum? Если вы заглядывали на Stack Overflow, то наверняка видели вопрос с тысячами просмотров: "How to get names of enum entries?". Казалось бы, что тут сложного? Но на практике многие новички (и не только) спотыкаются на этом месте.

В этой статье мы не просто дадим готовый ответ. Мы залезем под капот TypeScript и разберем, как именно работают enum, как компилятор превращает их в JavaScript, и почему некоторые подходы к получению имен могут привести к неожиданным багам. Готовы? Поехали!

Что такое enum в TypeScript и зачем нужны их имена?

Enum (перечисление) — это удобный способ задать набор именованных констант. В TypeScript они бывают числовыми (по умолчанию) и строковыми. Но что делать, если вам нужно получить имя конкретного значения? Например, вы получили из API код статуса 403, а хотите вывести пользователю понятное сообщение "Forbidden".

enum HttpStatus {
  OK = 200,
  NotFound = 404,
  Forbidden = 403
}

// Наша задача: получить имя "Forbidden" по значению 403

Кажется, что TypeScript должен предоставить встроенный метод для этого. Но нет. Приходится писать свою логику. И тут начинается самое интересное.

Решение "в лоб": Object.keys и Object.values

Самый очевидный способ — использовать методы Object.keys() или Object.values(). Многие новички пишут что-то вроде этого:

function getEnumName(enumObj: any, value: number): string | undefined {
  const keys = Object.keys(enumObj);
  return keys.find(key => enumObj[key] === value);
}

console.log(getEnumName(HttpStatus, 403)); // "Forbidden" — работает!

Выглядит логично? Да. Но если вы попробуете этот код на числовом enum, вас ждет сюрприз. Давайте посмотрим, во что компилируется наш HttpStatus.

Под капотом: компиляция числового enum

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

var HttpStatus;
(function (HttpStatus) {
    HttpStatus[HttpStatus["OK"] = 200] = "OK";
    HttpStatus[HttpStatus["NotFound"] = 404] = "NotFound";
    HttpStatus[HttpStatus["Forbidden"] = 403] = "Forbidden";
})(HttpStatus || (HttpStatus = {}));

// В итоге объект HttpStatus выглядит так:
// { 
//   "200": "OK", 
//   "404": "NotFound", 
//   "403": "Forbidden", 
//   "OK": 200, 
//   "NotFound": 404, 
//   "Forbidden": 403 
// }

Видите фокус? Компилятор добавляет и ключи-числа, и ключи-строки. Поэтому Object.keys(HttpStatus) вернет не только ["OK", "NotFound", "Forbidden"], но и ["200", "404", "403"]. Это ломает логику нашего простого решения!

Чтобы обойти это, нужно отфильтровать числовые ключи:

function getEnumNameSafe(enumObj: any, value: number): string | undefined {
  const keys = Object.keys(enumObj).filter(key => isNaN(Number(key)));
  return keys.find(key => enumObj[key] === value);
}

Но такой код выглядит костыльно. Есть ли способ лучше?

Строковые enum: другая история

Если вы используете строковые enum, то reverse mapping не создается. Это упрощает задачу:

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

// Object.keys(Direction) вернет только ["Up", "Down", "Left", "Right"] — идеально!

Но что, если вам нужно получить имя по значению? Тот же самый Object.keys отлично работает:

function getStringEnumName(enumObj: any, value: string): string | undefined {
  const keys = Object.keys(enumObj);
  return keys.find(key => enumObj[key] === value);
}

console.log(getStringEnumName(Direction, "DOWN")); // "Down"

Мораль: если вам нужно часто получать имена enum значений, подумайте о переходе на строковые enum. Они более предсказуемы.

Продвинутый подход: TypeScript утилиты и дженерики

Чтобы не писать велосипеды каждый раз, можно создать универсальную утилиту с использованием дженериков. Это не только красиво, но и безопасно по типам:

type EnumObject = { [key: string]: number | string };

function getEnumName<T extends EnumObject>(
  enumObj: T, 
  value: T[keyof T]
): keyof T | undefined {
  const keys = Object.keys(enumObj) as Array<keyof T>;
  return keys.find(key => enumObj[key] === value);
}

// Использование:
enum Status { Active = 1, Inactive = 0 }
const name = getEnumName(Status, 1); // TypeScript знает, что имя — "Active"
console.log(name); // "Active"

Обратите внимание: мы используем keyof T для возвращаемого типа. Это дает автодополнение и защиту от ошибок в IDE. Но есть нюанс: для числовых enum эта функция все равно будет возвращать и числа (ключи reverse mapping). Поэтому лучше использовать ее только со строковыми enum или добавить дополнительную фильтрацию.

Рефакторинг: Enum как const объект

Многие опытные разработчики в TypeScript сообществе (включая авторов популярных библиотек) рекомендуют избегать нативных enum в пользу const объектов с as const. Это дает больше контроля и предсказуемости:

const Colors = {
  Red: '#FF0000',
  Green: '#00FF00',
  Blue: '#0000FF'
} as const;

type Colors = typeof Colors[keyof typeof Colors]; // "#FF0000" | "#00FF00" | "#0000FF"

function getColorName(colorValue: Colors): string | undefined {
  const entry = Object.entries(Colors).find(([_, value]) => value === colorValue);
  return entry ? entry[0] : undefined;
}

console.log(getColorName('#00FF00')); // "Green"

Преимущества такого подхода:

  • Нет reverse mapping — объект остается "чистым".
  • Полная типобезопасность — TypeScript выводит точные типы.
  • Гибкость — можно легко добавить методы или метаданные прямо в объект.
  • Производительность — меньше магии при компиляции.

Минус: вы теряете синтаксис enum и некоторые фичи (например, автоматическую инкрементацию). Но для большинства случаев это лучший выбор.

Типичные ошибки и как их избежать

Давайте разберем частые грабли, на которые наступают разработчики:

Ошибка

Блоги

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