Зачем проверять значение в Enum TypeScript? Живой пример из практики
Представьте: вы пишете бэкенд на Node.js с TypeScript. Клиент присылает статус заказа — строку "shipped". У вас есть enum OrderStatus с вариантами: Pending, Processing, Shipped, Delivered. Но вдруг приходит "CANCELLED" или "unknown". Что делать? Правильно — проверить, существует ли значение в enum TypeScript.
Эта задача — одна из самых частых на Stack Overflow (тысячи просмотров). И не зря: enum в TypeScript — не просто набор констант, а хитрый зверь с числовыми и строковыми вариантами. В этом руководстве мы разберем все способы проверки наличия значения в enum, от простых до продвинутых, с примерами кода и пояснениями.
Если вы только начинаете изучать TypeScript или хотите углубить знания — эта статья для вас. Поехали!
Что такое Enum в TypeScript? Краткий ликбез
Enum (перечисление) — это способ задать набор именованных констант. В TypeScript есть два типа enum:
- Числовые enum — значения автоматически инкрементируются (0, 1, 2...).
- Строковые enum — каждому члену явно присваивается строка.
Пример числового enum:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
Пример строкового enum:
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING"
}
Зачем вообще проверять значение? Потому что данные извне (API, формы, базы данных) могут быть любыми. Без проверки вы рискуете получить undefined или ошибку выполнения.
Способ 1. Классика: Object.values() + includes()
Самый популярный и читаемый способ — превратить enum в массив значений и проверить вхождение с помощью includes().
Для строковых enum:
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
function isColor(value: string): boolean {
return Object.values(Color).includes(value as Color);
}
console.log(isColor("RED")); // true
console.log(isColor("YELLOW")); // false
Для числовых enum — хитрость! Object.values() возвращает обратную маппинг (ключи и значения). Поэтому нужно фильтровать только числа:
enum NumberEnum {
A = 1,
B = 2,
C = 3
}
function isNumberEnumValue(value: number): boolean {
return Object.values(NumberEnum).filter(v => typeof v === 'number').includes(value);
}
console.log(isNumberEnumValue(2)); // true
console.log(isNumberEnumValue(5)); // false
Этот подход универсален, но требует осторожности с числовыми enum. Плюс: простой и наглядный. Минус: нужно помнить про фильтрацию.
Способ 2. Type Guard: проверка через ключи enum
Если вам нужно не просто проверить значение, но и получить гарантию типа, используйте type guard. Этот метод особенно полезен при работе с API, где вы хотите сузить тип.
enum UserRole {
Admin = "admin",
User = "user",
Guest = "guest"
}
function isUserRole(value: string): value is UserRole {
return Object.values(UserRole).includes(value as UserRole);
}
function processRole(role: string) {
if (isUserRole(role)) {
// TypeScript знает, что role — это UserRole
console.log(`Роль: ${role}`);
} else {
console.log("Неизвестная роль");
}
}
processRole("admin"); // Роль: admin
processRole("moderator"); // Неизвестная роль
Этот подход дает безопасность типов и сокращает количество проверок в коде. Рекомендую для серьезных проектов.
Способ 3. Кастомный хелпер: универсальная функция
Чтобы не писать однотипные проверки для каждого enum, создайте универсальную функцию-хелпер. Это лучшая практика для больших кодовых баз.
// Утилита для проверки значений в любом enum
function enumContains<T extends Record<string, string | number>>(
enumObj: T,
value: string | number
): value is T[keyof T] {
return Object.values(enumObj).includes(value as T[keyof T]);
}
// Использование
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
enum Direction {
North = "NORTH",
South = "SOUTH"
}
console.log(enumContains(HttpStatus, 200)); // true
console.log(enumContains(HttpStatus, 403)); // false
console.log(enumContains(Direction, "SOUTH")); // true
console.log(enumContains(Direction, "EAST")); // false
Этот хелпер работает и с числами, и со строками. Совет: вынесите его в отдельный модуль utils/enum.ts и используйте во всем проекте.
Способ 4. Для числовых enum: проверка через обратный маппинг
Числовые enum в TypeScript создают обратный маппинг — вы можете получить ключ по значению. Это можно использовать для проверки.
enum StatusCode {
Success = 1,
Failure = 2,
Pending = 3
}
function isStatusCode(value: number): boolean {
return value in StatusCode;
}
console.log(isStatusCode(1)); // true (ключ "Success")
console.log(isStatusCode(4)); // false
Но будьте осторожны: оператор in проверяет ключи, а для числовых enum ключи — это строки "1", "2" и т.д. Поэтому value in StatusCode работает только для чисел, которые являются значениями enum. Минус: не работает для строковых enum (там нет обратного маппинга).
Способ 5. Использование Map или Set для производительности
Если вы проверяете значения десятки тысяч раз (например, в цикле обработки данных), массив includes() может быть медленным. Решение — создать Set или Map из значений enum один раз и использовать его.
enum PaymentMethod {
Card = "card",
Cash = "cash",
Crypto = "crypto"
}
// Создаем Set для быстрой проверки
const paymentMethodSet = new Set(Object.values(PaymentMethod));
function isValidPaymentMethod(value: string): boolean {
return paymentMethodSet.has(value);
}
// Тестирование
console.log(isValidPaymentMethod("card")); // true
console.log(isValidPaymentMethod("bitcoin")); // false
Для числовых enum используйте ту же технику, но с фильтрацией (как в способе 1). Производительность: Set.has() — O(1), Array.includes() — O(n). Разница заметна на больших данных.