Как правильно реализовать константы класса в TypeScript?
Правильная реализация констант класса в TypeScript
В TypeScript нет прямого аналога static const из других языков, но есть несколько надежных способов реализовать константы класса. Давайте разберем лучшие практики.
Способ 1: static readonly (рекомендуемый базовый подход)
Комбинация static и readonly — это стандартный способ объявления констант класса:
class UserManager {
// Константы класса
static readonly MAX_LOGIN_ATTEMPTS = 5;
static readonly DEFAULT_TIMEOUT = 3000;
// Константы с явным типом
static readonly STATUS_PENDING: string = 'pending';
static readonly STATUS_APPROVED: string = 'approved';
// Константы-объекты с readonly
static readonly CONFIG = {
apiUrl: 'https://api.example.com',
version: 'v2'
} as const;
static checkLoginAttempts(attempts: number): boolean {
return attempts <= UserManager.MAX_LOGIN_ATTEMPTS;
}
}
// Использование
console.log(UserManager.MAX_LOGIN_ATTEMPTS); // 5
console.log(UserManager.STATUS_PENDING); // 'pending'
Почему это работает? readonly запрещает изменение свойства в TypeScript на этапе компиляции, а static делает его принадлежащим классу.
Способ 2: Полная защита с Object.freeze
Для runtime-защиты от изменений (даже через прототип) используйте Object.freeze:
class OrderManager {
static readonly STATUSES = Object.freeze({
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected',
SHIPPED: 'shipped'
} as const);
static readonly MAX_ITEMS = Object.freeze(10);
// Защита самого класса от расширения
static {
Object.freeze(OrderManager);
}
}
// Попытка изменить выдаст ошибку в strict mode
// OrderManager.STATUSES.PENDING = 'new'; // TypeError в runtime
// OrderManager.MAX_ITEMS = 20; // TypeError в runtime
Способ 3: Enum-подобные константы (рекомендуется для групп констант)
Для набора связанных констант лучше использовать enum или namespace:
class PaymentProcessor {
// Используем вложенный namespace для группировки
static readonly Status = {
PENDING: 'pending' as const,
COMPLETED: 'completed' as const,
FAILED: 'failed' as const
} as const;
// Альтернатива с enum внутри класса
static readonly ErrorCode = {
INVALID_AMOUNT: 1001,
INSUFFICIENT_FUNDS: 1002,
NETWORK_ERROR: 1003
} as const;
static processPayment(amount: number): string {
if (amount <= 0) {
return PaymentProcessor.ErrorCode.INVALID_AMOUNT.toString();
}
return PaymentProcessor.Status.COMPLETED;
}
}
// Использование с type inference
type PaymentStatus = typeof PaymentProcessor.Status[keyof typeof PaymentProcessor.Status];
// PaymentStatus = 'pending' | 'completed' | 'failed'
Способ 4: Модульные константы (самый безопасный)
Для максимальной изоляции вынесите константы на уровень модуля:
// constants.ts
export const USER_CONSTANTS = {
MAX_LOGIN_ATTEMPTS: 5,
SESSION_TIMEOUT: 3600,
ROLES: {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest'
}
} as const;
// user-manager.ts
import { USER_CONSTANTS } from './constants';
class UserManager {
static readonly MAX_LOGIN_ATTEMPTS = USER_CONSTANTS.MAX_LOGIN_ATTEMPTS;
static login(username: string, password: string): boolean {
// Используем константы
return true;
}
}
Важные моменты:
- readonly vs const:
readonlyдля свойств класса,constдля локальных переменных - as const: Сужает типы до литералов, делая объекты глубоко неизменяемыми на уровне типов
- Object.freeze: Защищает от изменений в runtime, но не от TypeScript ошибок
- static блоки: Позволяют выполнить инициализацию констант при загрузке класса
Рекомендация: Для простых случаев используйте static readonly. Для групп констант — as const с Object.freeze. Для перечислений — нативные enum TypeScript.
Альтернативный подход: Использование абстрактного класса с enum
Если вам нужна строгая типизация и автодополнение в IDE, рассмотрите комбинацию enum с классом:
// Определяем enum для статусов
enum OrderStatus {
PENDING = 'pending',
APPROVED = 'approved',
REJECTED = 'rejected'
}
// Класс использует enum как источник констант
abstract class OrderConstants {
static readonly Status = OrderStatus;
static readonly MAX_ITEMS = 50;
static readonly MIN_AMOUNT = 10;
// Запрещаем создание экземпляров
private constructor() {}
}
// Использование
console.log(OrderConstants.Status.PENDING); // 'pending'
console.log(OrderConstants.MAX_ITEMS); // 50
// Type safety
function updateOrderStatus(status: OrderStatus) {
// Только 'pending' | 'approved' | 'rejected'
}
Преимущества этого подхода:
enumобеспечивает строгую типизацию и автодополнение- Абстрактный класс с приватным конструктором предотвращает создание экземпляров
- Константы логически сгруппированы внутри класса
- TypeScript компилятор гарантирует неизменяемость enum значений
Недостаток: Enum компилируется в JavaScript объект, что добавляет небольшой overhead. Для pure констант лучше использовать as const.