По умолчанию enum — числовой, значения начинаются с 0 и автоматически инкрементируются:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
const dir: Direction = Direction.Up
console.log(dir) // 0
console.log(Direction[0]) // 'Up' — reverse mapping!Можно задать начальное значение:
enum HttpStatus {
OK = 200,
NotFound = 404,
InternalError = 500,
}**Reverse mapping** — уникальная особенность числовых enum: по числу можно получить имя:
console.log(Direction[2]) // 'Left'
console.log(Direction['Left']) // 2enum OrderStatus {
Pending = 'PENDING',
Confirmed = 'CONFIRMED',
Shipped = 'SHIPPED',
Delivered = 'DELIVERED',
Cancelled = 'CANCELLED',
}
const status: OrderStatus = OrderStatus.Pending
console.log(status) // 'PENDING'
// Нет reverse mapping у строковых enum!Строковые enum предпочтительнее: значения читаемы в логах, нет неожиданного reverse mapping.
const enum Color {
Red = 'RED',
Green = 'GREEN',
Blue = 'BLUE',
}
// Использование:
const c = Color.Red // Компилируется в: const c = 'RED'
// Объект Color НЕ создаётся в runtime!**Важно**: const enum нельзя использовать через barrel re-exports и с isolatedModules (Vite, esbuild). В проектах на Vite используйте обычный enum или as const.
enum Fruit {
Apple = 0,
Banana = 1,
}
// Числовые enum принимают любое число — баг!
function eat(fruit: Fruit) { }
eat(999) // TypeScript не ругается! Баг в дизайне.
// Строковые enum безопаснее:
enum FruitStr {
Apple = 'APPLE',
}
eat2('APPLE') // Ошибка TS — нужно FruitStr.AppleEnum **увеличивает bundle** — компилируется в IIFE объект:
// Скомпилированный enum Direction:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));Современный TypeScript рекомендует as const вместо enum:
// Вместо enum:
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const
// Тип значений:
type Direction = typeof Direction[keyof typeof Direction]
// 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
function move(dir: Direction) { }
move(Direction.Up) // OK
move('UP') // OK — литерал тоже принимается
// move('diagonal') // Ошибка TS
// Union type — ещё проще:
type Status = 'pending' | 'success' | 'error'| Сценарий | Рекомендация |
|---|---|
| Новый проект | as const + union type |
| Нужен reverse mapping | числовой enum |
| Компиляция без bundler | const enum |
| Легаси код / Angular | enum |
State Machine для заказа с enum-like объектами (Object.freeze) и валидацией переходов
// В TypeScript: enum OrderStatus { Pending = 'PENDING', ... }
// В JS: Object.freeze имитирует const enum
const OrderStatus = Object.freeze({
Pending: 'PENDING',
Confirmed: 'CONFIRMED',
Shipped: 'SHIPPED',
Delivered: 'DELIVERED',
Cancelled: 'CANCELLED',
})
// Допустимые переходы между статусами
const TRANSITIONS = Object.freeze({
[OrderStatus.Pending]: [OrderStatus.Confirmed, OrderStatus.Cancelled],
[OrderStatus.Confirmed]: [OrderStatus.Shipped, OrderStatus.Cancelled],
[OrderStatus.Shipped]: [OrderStatus.Delivered],
[OrderStatus.Delivered]: [], // финальное состояние
[OrderStatus.Cancelled]: [], // финальное состояние
})
const STATUS_LABELS = Object.freeze({
[OrderStatus.Pending]: 'Ожидает подтверждения',
[OrderStatus.Confirmed]: 'Подтверждён',
[OrderStatus.Shipped]: 'Отправлен',
[OrderStatus.Delivered]: 'Доставлен',
[OrderStatus.Cancelled]: 'Отменён',
})
class Order {
#status
#history
constructor(id) {
this.id = id
this.#status = OrderStatus.Pending
this.#history = [{ status: this.#status, timestamp: new Date() }]
}
get status() { return this.#status }
get label() { return STATUS_LABELS[this.#status] }
canTransitionTo(newStatus) {
const allowed = TRANSITIONS[this.#status] ?? []
return allowed.includes(newStatus)
}
transition(newStatus) {
// Валидация: статус должен существовать
if (!Object.values(OrderStatus).includes(newStatus)) {
throw new Error(`Неизвестный статус: ${newStatus}`)
}
// Валидация: переход должен быть допустимым
if (!this.canTransitionTo(newStatus)) {
throw new Error(
`Нельзя перейти из ${this.#status} в ${newStatus}`
)
}
this.#status = newStatus
this.#history.push({ status: newStatus, timestamp: new Date() })
return this
}
confirm() { return this.transition(OrderStatus.Confirmed) }
ship() { return this.transition(OrderStatus.Shipped) }
deliver() { return this.transition(OrderStatus.Delivered) }
cancel() { return this.transition(OrderStatus.Cancelled) }
getHistory() {
return this.#history.map(h => `${STATUS_LABELS[h.status]}`).join(' → ')
}
}
// --- Демонстрация ---
console.log('=== Успешный заказ ===')
const order1 = new Order(1001)
console.log(`Статус: ${order1.label}`)
order1.confirm()
console.log(`Статус: ${order1.label}`)
order1.ship()
console.log(`Статус: ${order1.label}`)
order1.deliver()
console.log(`Статус: ${order1.label}`)
console.log(`История: ${order1.getHistory()}`)
console.log('\n=== Отменённый заказ ===')
const order2 = new Order(1002)
order2.confirm().cancel()
console.log(`История: ${order2.getHistory()}`)
console.log('\n=== Попытки недопустимых переходов ===')
const order3 = new Order(1003)
try {
order3.ship() // Нельзя: Pending → Shipped
} catch (e) {
console.log(`Ошибка: ${e.message}`)
}
order3.confirm()
// Нельзя: Confirmed → Delivered (пропускаем Shipped)
try {
order3.deliver()
} catch (e) {
console.log(`Ошибка: ${e.message}`)
}
console.log('\n=== Проверка допустимых переходов ===')
const order4 = new Order(1004)
const allStatuses = Object.values(OrderStatus)
allStatuses.forEach(s => {
const can = order4.canTransitionTo(s)
if (can) console.log(` Можно: ${OrderStatus.Pending} → ${s}`)
})По умолчанию enum — числовой, значения начинаются с 0 и автоматически инкрементируются:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
const dir: Direction = Direction.Up
console.log(dir) // 0
console.log(Direction[0]) // 'Up' — reverse mapping!Можно задать начальное значение:
enum HttpStatus {
OK = 200,
NotFound = 404,
InternalError = 500,
}**Reverse mapping** — уникальная особенность числовых enum: по числу можно получить имя:
console.log(Direction[2]) // 'Left'
console.log(Direction['Left']) // 2enum OrderStatus {
Pending = 'PENDING',
Confirmed = 'CONFIRMED',
Shipped = 'SHIPPED',
Delivered = 'DELIVERED',
Cancelled = 'CANCELLED',
}
const status: OrderStatus = OrderStatus.Pending
console.log(status) // 'PENDING'
// Нет reverse mapping у строковых enum!Строковые enum предпочтительнее: значения читаемы в логах, нет неожиданного reverse mapping.
const enum Color {
Red = 'RED',
Green = 'GREEN',
Blue = 'BLUE',
}
// Использование:
const c = Color.Red // Компилируется в: const c = 'RED'
// Объект Color НЕ создаётся в runtime!**Важно**: const enum нельзя использовать через barrel re-exports и с isolatedModules (Vite, esbuild). В проектах на Vite используйте обычный enum или as const.
enum Fruit {
Apple = 0,
Banana = 1,
}
// Числовые enum принимают любое число — баг!
function eat(fruit: Fruit) { }
eat(999) // TypeScript не ругается! Баг в дизайне.
// Строковые enum безопаснее:
enum FruitStr {
Apple = 'APPLE',
}
eat2('APPLE') // Ошибка TS — нужно FruitStr.AppleEnum **увеличивает bundle** — компилируется в IIFE объект:
// Скомпилированный enum Direction:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));Современный TypeScript рекомендует as const вместо enum:
// Вместо enum:
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const
// Тип значений:
type Direction = typeof Direction[keyof typeof Direction]
// 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
function move(dir: Direction) { }
move(Direction.Up) // OK
move('UP') // OK — литерал тоже принимается
// move('diagonal') // Ошибка TS
// Union type — ещё проще:
type Status = 'pending' | 'success' | 'error'| Сценарий | Рекомендация |
|---|---|
| Новый проект | as const + union type |
| Нужен reverse mapping | числовой enum |
| Компиляция без bundler | const enum |
| Легаси код / Angular | enum |
State Machine для заказа с enum-like объектами (Object.freeze) и валидацией переходов
// В TypeScript: enum OrderStatus { Pending = 'PENDING', ... }
// В JS: Object.freeze имитирует const enum
const OrderStatus = Object.freeze({
Pending: 'PENDING',
Confirmed: 'CONFIRMED',
Shipped: 'SHIPPED',
Delivered: 'DELIVERED',
Cancelled: 'CANCELLED',
})
// Допустимые переходы между статусами
const TRANSITIONS = Object.freeze({
[OrderStatus.Pending]: [OrderStatus.Confirmed, OrderStatus.Cancelled],
[OrderStatus.Confirmed]: [OrderStatus.Shipped, OrderStatus.Cancelled],
[OrderStatus.Shipped]: [OrderStatus.Delivered],
[OrderStatus.Delivered]: [], // финальное состояние
[OrderStatus.Cancelled]: [], // финальное состояние
})
const STATUS_LABELS = Object.freeze({
[OrderStatus.Pending]: 'Ожидает подтверждения',
[OrderStatus.Confirmed]: 'Подтверждён',
[OrderStatus.Shipped]: 'Отправлен',
[OrderStatus.Delivered]: 'Доставлен',
[OrderStatus.Cancelled]: 'Отменён',
})
class Order {
#status
#history
constructor(id) {
this.id = id
this.#status = OrderStatus.Pending
this.#history = [{ status: this.#status, timestamp: new Date() }]
}
get status() { return this.#status }
get label() { return STATUS_LABELS[this.#status] }
canTransitionTo(newStatus) {
const allowed = TRANSITIONS[this.#status] ?? []
return allowed.includes(newStatus)
}
transition(newStatus) {
// Валидация: статус должен существовать
if (!Object.values(OrderStatus).includes(newStatus)) {
throw new Error(`Неизвестный статус: ${newStatus}`)
}
// Валидация: переход должен быть допустимым
if (!this.canTransitionTo(newStatus)) {
throw new Error(
`Нельзя перейти из ${this.#status} в ${newStatus}`
)
}
this.#status = newStatus
this.#history.push({ status: newStatus, timestamp: new Date() })
return this
}
confirm() { return this.transition(OrderStatus.Confirmed) }
ship() { return this.transition(OrderStatus.Shipped) }
deliver() { return this.transition(OrderStatus.Delivered) }
cancel() { return this.transition(OrderStatus.Cancelled) }
getHistory() {
return this.#history.map(h => `${STATUS_LABELS[h.status]}`).join(' → ')
}
}
// --- Демонстрация ---
console.log('=== Успешный заказ ===')
const order1 = new Order(1001)
console.log(`Статус: ${order1.label}`)
order1.confirm()
console.log(`Статус: ${order1.label}`)
order1.ship()
console.log(`Статус: ${order1.label}`)
order1.deliver()
console.log(`Статус: ${order1.label}`)
console.log(`История: ${order1.getHistory()}`)
console.log('\n=== Отменённый заказ ===')
const order2 = new Order(1002)
order2.confirm().cancel()
console.log(`История: ${order2.getHistory()}`)
console.log('\n=== Попытки недопустимых переходов ===')
const order3 = new Order(1003)
try {
order3.ship() // Нельзя: Pending → Shipped
} catch (e) {
console.log(`Ошибка: ${e.message}`)
}
order3.confirm()
// Нельзя: Confirmed → Delivered (пропускаем Shipped)
try {
order3.deliver()
} catch (e) {
console.log(`Ошибка: ${e.message}`)
}
console.log('\n=== Проверка допустимых переходов ===')
const order4 = new Order(1004)
const allStatuses = Object.values(OrderStatus)
allStatuses.forEach(s => {
const can = order4.canTransitionTo(s)
if (can) console.log(` Можно: ${OrderStatus.Pending} → ${s}`)
})Реализуй FSM (Finite State Machine) для светофора. Используй `Object.freeze` для создания "enum" состояний: RED, YELLOW, GREEN. Функция `nextState(current)` возвращает следующее состояние (red → green → yellow → red). Функция `isValid(state)` возвращает true если состояние допустимо.
Object.freeze({ Red: "red", Yellow: "yellow", Green: "green" }). Для nextState используй объект-маппинг: const next = { red: "green", green: "yellow", yellow: "red" }; return next[current]. Для isValid: Object.values(TrafficLight).includes(state).
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке