type создаёт псевдоним — новое имя для существующего типа или описывает сложный тип:
// Простой псевдоним примитива
type UserId = number
type Email = string
// Объектный тип
type User = {
id: UserId
name: string
email: Email
age: number
}
const user: User = { id: 1, name: 'Алексей', email: 'alex@mail.ru', age: 30 }type Profile = {
readonly id: number // нельзя изменить после создания
name: string
age?: number // опциональное — может быть undefined
bio?: string // опциональное
}
const profile: Profile = { id: 1, name: 'Алексей' }
profile.age = 30 // OK
profile.id = 2 // Ошибка: Cannot assign to 'id' because it is a read-only propertyUnion позволяет переменной иметь один из нескольких типов:
// Строковый union — только указанные значения
type Status = 'active' | 'inactive' | 'banned'
type Direction = 'left' | 'right' | 'up' | 'down'
// Union примитивов
type Id = string | number // строка или число
// Работа с union — TypeScript требует проверки:
function formatId(id: Id): string {
if (typeof id === 'string') {
return id.toUpperCase() // TypeScript знает что id — string
}
return id.toString() // TypeScript знает что id — number
}Intersection объединяет несколько типов в один (объект должен иметь все поля):
type User = { name: string; email: string }
type Admin = { role: 'admin'; permissions: string[] }
type AdminUser = User & Admin
// AdminUser = { name: string; email: string; role: 'admin'; permissions: string[] }
const admin: AdminUser = {
name: 'Мария',
email: 'maria@mail.ru',
role: 'admin',
permissions: ['read', 'write', 'delete'],
}Оба описывают форму объекта. Ключевые различия:
// type — закрытый, но поддерживает union и mapped types
type Status = 'active' | 'inactive' // union — только через type
type UserKeys = keyof User // mapped type — только через type
// interface — открытый, поддерживает declaration merging
interface Animal { name: string }
interface Animal { age: number } // OK! Расширяет предыдущий
// Animal теперь { name: string; age: number }
// type нельзя расширить повторным объявлением:
type Animal = { name: string }
type Animal = { age: number } // Ошибка: Duplicate identifier 'Animal'**Правило**: используй interface для объектов и классов, type для union, intersection и примитивных псевдонимов.
type Priority = 1 | 2 | 3
type TaskStatus = 'todo' | 'in-progress' | 'done'
type Task = {
readonly id: number
title: string
status: TaskStatus
priority: Priority
assignee?: string // опциональный
}
function createTask(title: string, priority: Priority): Task {
return {
id: Date.now(),
title,
status: 'todo',
priority,
}
}
function updateStatus(task: Task, status: TaskStatus): Task {
return { ...task, status } // не мутируем, возвращаем новый объект
}Система управления пользователями с runtime-валидацией объектных типов, union значений и intersection
// В TypeScript типы проверяются при компиляции.
// Реализуем эквивалентную runtime-валидацию.
// Симуляция union type: Status = 'active' | 'inactive' | 'banned'
const VALID_STATUSES = ['active', 'inactive', 'banned']
// Симуляция union type: Role = 'viewer' | 'editor' | 'admin'
const VALID_ROLES = ['viewer', 'editor', 'admin']
// Симуляция readonly + optional: создаёт User объект с валидацией
function createUser(name, email, role = 'viewer') {
if (typeof name !== 'string' || name.trim() === '') {
throw new TypeError('name должен быть непустой строкой')
}
if (typeof email !== 'string' || !email.includes('@')) {
throw new TypeError('email должен содержать @')
}
if (!VALID_ROLES.includes(role)) {
throw new TypeError(`role должен быть одним из: ${VALID_ROLES.join(', ')}`)
}
// Object.freeze симулирует readonly поля
return Object.freeze({
id: Date.now(),
name: name.trim(),
email: email.toLowerCase(),
role,
status: 'active',
createdAt: new Date().toISOString(),
})
}
// Симуляция intersection: AdminUser = User & AdminRights
function promoteToAdmin(user, permissions) {
if (!Array.isArray(permissions)) {
throw new TypeError('permissions должен быть массивом')
}
// Возвращаем новый объект = User & AdminRights (intersection)
return Object.freeze({
...user,
role: 'admin',
permissions: Object.freeze([...permissions]),
promotedAt: new Date().toISOString(),
})
}
// Симуляция функции принимающей union type: Status
function updateStatus(user, status) {
if (!VALID_STATUSES.includes(status)) {
throw new TypeError(`Некорректный статус. Доступны: ${VALID_STATUSES.join(', ')}`)
}
return Object.freeze({ ...user, status })
}
// Форматирует ID: Id = string | number
function formatId(id) {
if (typeof id === 'string') return id.toUpperCase()
if (typeof id === 'number') return `#${id.toString().padStart(6, '0')}`
throw new TypeError('id должен быть string или number')
}
// --- Демонстрация ---
console.log('=== Создание пользователей ===')
const alice = createUser('Алиса', 'alice@mail.ru')
const bob = createUser('Боб', 'bob@mail.ru', 'editor')
console.log(alice)
console.log(bob)
console.log('\n=== Intersection: промоция в admin ===')
const adminAlice = promoteToAdmin(alice, ['read', 'write', 'delete'])
console.log(adminAlice)
console.log('\n=== Union: обновление статуса ===')
const bannedBob = updateStatus(bob, 'banned')
console.log(`${bannedBob.name}: ${bannedBob.status}`)
try {
updateStatus(alice, 'suspended') // TypeError — не в union
} catch (e) {
console.log(e.message)
}
console.log('\n=== Union type Id: string | number ===')
console.log(formatId('usr_abc')) // 'USR_ABC'
console.log(formatId(42)) // '#000042'
console.log('\n=== Readonly — нельзя мутировать ===')
try {
alice.name = 'Другое имя' // В strict mode: TypeError
} catch (e) {
console.log('Попытка изменить readonly:', e.message)
}
console.log('alice.name:', alice.name) // Алиса — не изменилосьtype создаёт псевдоним — новое имя для существующего типа или описывает сложный тип:
// Простой псевдоним примитива
type UserId = number
type Email = string
// Объектный тип
type User = {
id: UserId
name: string
email: Email
age: number
}
const user: User = { id: 1, name: 'Алексей', email: 'alex@mail.ru', age: 30 }type Profile = {
readonly id: number // нельзя изменить после создания
name: string
age?: number // опциональное — может быть undefined
bio?: string // опциональное
}
const profile: Profile = { id: 1, name: 'Алексей' }
profile.age = 30 // OK
profile.id = 2 // Ошибка: Cannot assign to 'id' because it is a read-only propertyUnion позволяет переменной иметь один из нескольких типов:
// Строковый union — только указанные значения
type Status = 'active' | 'inactive' | 'banned'
type Direction = 'left' | 'right' | 'up' | 'down'
// Union примитивов
type Id = string | number // строка или число
// Работа с union — TypeScript требует проверки:
function formatId(id: Id): string {
if (typeof id === 'string') {
return id.toUpperCase() // TypeScript знает что id — string
}
return id.toString() // TypeScript знает что id — number
}Intersection объединяет несколько типов в один (объект должен иметь все поля):
type User = { name: string; email: string }
type Admin = { role: 'admin'; permissions: string[] }
type AdminUser = User & Admin
// AdminUser = { name: string; email: string; role: 'admin'; permissions: string[] }
const admin: AdminUser = {
name: 'Мария',
email: 'maria@mail.ru',
role: 'admin',
permissions: ['read', 'write', 'delete'],
}Оба описывают форму объекта. Ключевые различия:
// type — закрытый, но поддерживает union и mapped types
type Status = 'active' | 'inactive' // union — только через type
type UserKeys = keyof User // mapped type — только через type
// interface — открытый, поддерживает declaration merging
interface Animal { name: string }
interface Animal { age: number } // OK! Расширяет предыдущий
// Animal теперь { name: string; age: number }
// type нельзя расширить повторным объявлением:
type Animal = { name: string }
type Animal = { age: number } // Ошибка: Duplicate identifier 'Animal'**Правило**: используй interface для объектов и классов, type для union, intersection и примитивных псевдонимов.
type Priority = 1 | 2 | 3
type TaskStatus = 'todo' | 'in-progress' | 'done'
type Task = {
readonly id: number
title: string
status: TaskStatus
priority: Priority
assignee?: string // опциональный
}
function createTask(title: string, priority: Priority): Task {
return {
id: Date.now(),
title,
status: 'todo',
priority,
}
}
function updateStatus(task: Task, status: TaskStatus): Task {
return { ...task, status } // не мутируем, возвращаем новый объект
}Система управления пользователями с runtime-валидацией объектных типов, union значений и intersection
// В TypeScript типы проверяются при компиляции.
// Реализуем эквивалентную runtime-валидацию.
// Симуляция union type: Status = 'active' | 'inactive' | 'banned'
const VALID_STATUSES = ['active', 'inactive', 'banned']
// Симуляция union type: Role = 'viewer' | 'editor' | 'admin'
const VALID_ROLES = ['viewer', 'editor', 'admin']
// Симуляция readonly + optional: создаёт User объект с валидацией
function createUser(name, email, role = 'viewer') {
if (typeof name !== 'string' || name.trim() === '') {
throw new TypeError('name должен быть непустой строкой')
}
if (typeof email !== 'string' || !email.includes('@')) {
throw new TypeError('email должен содержать @')
}
if (!VALID_ROLES.includes(role)) {
throw new TypeError(`role должен быть одним из: ${VALID_ROLES.join(', ')}`)
}
// Object.freeze симулирует readonly поля
return Object.freeze({
id: Date.now(),
name: name.trim(),
email: email.toLowerCase(),
role,
status: 'active',
createdAt: new Date().toISOString(),
})
}
// Симуляция intersection: AdminUser = User & AdminRights
function promoteToAdmin(user, permissions) {
if (!Array.isArray(permissions)) {
throw new TypeError('permissions должен быть массивом')
}
// Возвращаем новый объект = User & AdminRights (intersection)
return Object.freeze({
...user,
role: 'admin',
permissions: Object.freeze([...permissions]),
promotedAt: new Date().toISOString(),
})
}
// Симуляция функции принимающей union type: Status
function updateStatus(user, status) {
if (!VALID_STATUSES.includes(status)) {
throw new TypeError(`Некорректный статус. Доступны: ${VALID_STATUSES.join(', ')}`)
}
return Object.freeze({ ...user, status })
}
// Форматирует ID: Id = string | number
function formatId(id) {
if (typeof id === 'string') return id.toUpperCase()
if (typeof id === 'number') return `#${id.toString().padStart(6, '0')}`
throw new TypeError('id должен быть string или number')
}
// --- Демонстрация ---
console.log('=== Создание пользователей ===')
const alice = createUser('Алиса', 'alice@mail.ru')
const bob = createUser('Боб', 'bob@mail.ru', 'editor')
console.log(alice)
console.log(bob)
console.log('\n=== Intersection: промоция в admin ===')
const adminAlice = promoteToAdmin(alice, ['read', 'write', 'delete'])
console.log(adminAlice)
console.log('\n=== Union: обновление статуса ===')
const bannedBob = updateStatus(bob, 'banned')
console.log(`${bannedBob.name}: ${bannedBob.status}`)
try {
updateStatus(alice, 'suspended') // TypeError — не в union
} catch (e) {
console.log(e.message)
}
console.log('\n=== Union type Id: string | number ===')
console.log(formatId('usr_abc')) // 'USR_ABC'
console.log(formatId(42)) // '#000042'
console.log('\n=== Readonly — нельзя мутировать ===')
try {
alice.name = 'Другое имя' // В strict mode: TypeError
} catch (e) {
console.log('Попытка изменить readonly:', e.message)
}
console.log('alice.name:', alice.name) // Алиса — не изменилосьРеализуй систему управления задачами. Функция `createTask(title, priority)` создаёт задачу с полями id, title, status ("todo"), priority (1|2|3). Функция `updateStatus(task, status)` возвращает новую задачу с обновлённым статусом. Валидируй входные данные.
В createTask: title.trim() === "" для проверки пустой строки. !VALID_PRIORITIES.includes(priority) для проверки priority. Для updateStatus: !VALID_STATUSES.includes(status) и возврат { ...task, status } создаст новый объект без мутации.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке