Avatar ·

Как проверить, существует ли значение в enum в TypeScript?

Всем привет! Я только начал изучать TypeScript и столкнулся с проблемой при работе с перечислениями (enum). У меня есть такой код: ```typescript enum Color { Red = 'RED', Green = 'GREEN', Blue = 'BLUE' } // Я получаю значение из внешнего источника (например, из API) const userInput: string = 'RED'; // или может быть 'YELLOW' // Мне нужно проверить, является ли userInput допустимым значением Color if (userInput === Color.Red) { console.log('Это красный'); } ``` Но проблема в том, что если я получу значение, которого нет в enum (например, 'YELLOW'), мне нужно корректно обработать эту ситуацию. Я пробовал использовать `Object.values(Color).includes(userInput)`, но TypeScript ругается, что `userInput` имеет тип `string`, а не `Color`. Подскажите, пожалуйста, как правильно проверить, есть ли значение в enum? Нужно ли использовать какие-то специальные утилиты или есть простое решение? Заранее спасибо! P.S. Я использую строковые enum, но было бы полезно узнать и про числовые тоже. <<>>

Проверка существования значения в enum: основные подходы

В TypeScript есть несколько способов проверить, существует ли значение в enum. Выбор подхода зависит от типа enum (строковый или числовой) и от того, как вы планируете использовать результат проверки.

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

Самый простой и надёжный способ — использовать Object.values() вместе с приведением типа:

enum Color {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE'
}

function isValidColor(value: string): value is Color {
  return Object.values(Color).includes(value as Color);
}

// Использование
const userInput: string = 'RED';
if (isValidColor(userInput)) {
  console.log(`Допустимый цвет: ${userInput}`);
  // Здесь TypeScript знает, что userInput — это Color
} else {
  console.log(`Недопустимое значение: ${userInput}`);
}

Ключевой момент: includes(value as Color) — это приведение типа, которое говорит TypeScript: "доверься мне, я знаю, что делаю". На самом деле в рантайме проверка работает корректно, так как Object.values(Color) возвращает массив строк ['RED', 'GREEN', 'BLUE'].

2. Для числовых enum

С числовыми enum ситуация сложнее, потому что TypeScript создаёт двунаправленное отображение (ключ → значение и значение → ключ):

enum Status {
  Active = 1,
  Inactive = 2,
  Pending = 3
}

// Object.values(Status) вернёт: [1, 2, 3, 'Active', 'Inactive', 'Pending']
// Это не очень удобно для проверки

function isValidStatus(value: number): value is Status {
  return Object.values(Status).includes(value as Status);
}

// Но лучше использовать более точную проверку:
function isValidStatusSafe(value: number): boolean {
  return [Status.Active, Status.Inactive, Status.Pending].includes(value);
}

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

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

function isEnumValue<T extends Record<string, string | number>>(
  enumObj: T,
  value: unknown
): value is T[keyof T] {
  const values = Object.values(enumObj);
  // Для числовых enum отфильтровываем строковые ключи
  const enumValues = values.filter(v => typeof v === 'number' || typeof v === 'string');
  return enumValues.includes(value as T[keyof T]);
}

// Использование
enum Color {
  Red = 'RED',
  Green = 'GREEN'
}

enum Status {
  Active = 1,
  Inactive = 2
}

console.log(isEnumValue(Color, 'RED'));    // true
console.log(isEnumValue(Color, 'YELLOW')); // false
console.log(isEnumValue(Status, 1));       // true
console.log(isEnumValue(Status, 5));       // false

4. Type Guard для автодополнения

Если вы хотите, чтобы после проверки TypeScript "знал" тип значения, используйте type guard:

function isColor(value: string): value is Color {
  return Object.values(Color).includes(value as Color);
}

const input: string = 'GREEN';
if (isColor(input)) {
  // Здесь input имеет тип Color, а не string
  console.log(input.toLowerCase()); // Автодополнение работает!
}

Важные замечания:

  • Для const enum (объявленных с ключевым словом const) этот подход не сработает, так как const enum не существует в рантайме. Вам придётся использовать обычный enum или массив констант.
  • Производительность: Object.values() создаёт новый массив при каждом вызове. Для частых проверок лучше кешировать массив значений.
  • Типобезопасность: Использование as — это компромисс. Если вы уверены в логике, это безопасно, но всегда тестируйте свой код.

В вашем случае рекомендую использовать первый подход с функцией isValidColor — он простой, понятный и эффективный для строковых enum.

<<>>

Альтернативный подход: использование Map или Set

Если вам нужно часто проверять значения enum и важна производительность, лучше заранее создать Set или Map со всеми допустимыми значениями:

enum Color {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE'
}

// Создаём Set один раз
const validColors = new Set(Object.values(Color));

function isValidColor(value: string): boolean {
  return validColors.has(value);
}

// Использование
console.log(isValidColor('RED'));    // true
console.log(isValidColor('YELLOW')); // false

Преимущества:

  • Быстрее, чем includes() при большом количестве проверок (O(1) vs O(n)).
  • Не нужно использовать приведение типов.
  • Легко расширять — просто добавьте новые значения в enum.

Недостаток: Не является type guard, поэтому после проверки TypeScript не будет знать точный тип. Если вам нужен type guard, комбинируйте оба подхода:

const validColors = new Set(Object.values(Color));

function isColor(value: string): value is Color {
  return validColors.has(value as Color);
}

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