На первый взгляд interface и type взаимозаменяемы:
// Оба описывают форму объекта
interface UserInterface {
id: number
name: string
}
type UserType = {
id: number
name: string
}
// Оба работают одинаково:
const u1: UserInterface = { id: 1, name: 'Алексей' }
const u2: UserType = { id: 1, name: 'Алексей' }Интерфейсы поддерживают слияние объявлений — несколько деклараций с одним именем объединяются автоматически:
interface Window {
myPlugin: () => void
}
// TypeScript добавит myPlugin к встроенному интерфейсу Window
interface Config {
host: string
}
interface Config {
port: number // добавляется к первому объявлению
}
// Итог: Config = { host: string; port: number }
// С type это невозможно:
type Config2 = { host: string }
// type Config2 = { port: number } // Ошибка: Duplicate identifier 'Config2'Declaration merging полезен при расширении сторонних библиотек.
Type aliases могут описывать то, что interface не может:
// Union type — только через type:
type StringOrNumber = string | number
type Status = 'active' | 'inactive' | 'pending'
// Пересечения через type:
type AdminUser = User & { permissions: string[] }
// Кортежи:
type Pair = [string, number]
// Primitive alias:
type UserId = string // interface не может алиасить примитив
// Conditional type:
type NonNullable<T> = T extends null | undefined ? never : TОба механизма расширяют тип, но с разным поведением при конфликте:
interface A { x: string }
interface B extends A { x: string; y: number } // OK — совместимый тип
type C = { x: string }
type D = C & { x: number } // Допускается, но x будет never (string & number)
// extends даёт ошибку при несовместимых типах — это лучше!
interface E { x: string }
// interface F extends E { x: number } // Ошибка TS — несовместимый тип обнаружен сразуКласс может реализовать interface или type-alias объекта:
interface Serializable {
serialize(): string
}
type HasId = { id: number }
class Document implements Serializable, HasId {
id = 0
serialize() { return JSON.stringify(this) }
}
// Оба работают с implementsИспользуй `interface`:
module augmentation)Используй `type`:
// Рекомендуемая практика:
interface User { // объект — interface
id: UserId
name: string
role: UserRole
}
type UserId = string // примитив — type
type UserRole = 'admin' | 'user' // union — type
type UserWithMeta = User & { // intersection — type
createdAt: Date
updatedAt: Date
}Паттерн расширения: базовый объект + дополнительные поля через Object.assign и spread — аналог interface extends и type intersection
// В TypeScript: interface extends и type intersection
// В JS — показываем runtime-аналоги этих паттернов
// --- Аналог interface extends ---
function createBase(id, name) {
return { id, name }
}
function createUser(id, name, email, role = 'user') {
// extends Base: добавляем поля поверх базы
return Object.assign(createBase(id, name), { email, role })
}
function createAdmin(id, name, email, permissions) {
// extends User: добавляем поля поверх User
return Object.assign(createUser(id, name, email, 'admin'), { permissions })
}
// --- Аналог type intersection (A & B) ---
function withTimestamps(obj) {
return Object.assign({}, obj, {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
}
function withSoftDelete(obj) {
return Object.assign({}, obj, {
deletedAt: null,
isDeleted: false,
})
}
// Intersection через spread: User & Timestamped & SoftDeletable
function createFullRecord(id, name, email) {
const base = createUser(id, name, email)
return withSoftDelete(withTimestamps(base))
}
// --- Declaration merging через Object.assign (расширение контракта) ---
// Базовый "интерфейс" для плагинов
const pluginRegistry = {}
function registerPlugin(name, plugin) {
pluginRegistry[name] = plugin
}
// Каждый модуль "расширяет" registry — аналог declaration merging
registerPlugin('logger', {
log: (msg) => console.log('[LOG]', msg),
warn: (msg) => console.log('[WARN]', msg),
})
registerPlugin('validator', {
required: (val) => val != null && val !== '',
email: (val) => /S+@S+.S+/.test(val),
})
// --- Демонстрация ---
console.log('=== Иерархия extends ===')
const user = createUser(1, 'Алексей', 'alex@mail.ru')
console.log('User:', user)
const admin = createAdmin(2, 'Ольга', 'olga@mail.ru', ['read', 'write', 'delete'])
console.log('Admin:', admin)
// Полиморфизм — оба имеют id и name:
const entities = [user, admin]
entities.forEach(e => console.log(` id=${e.id}, name=${e.name}, role=${e.role}`))
console.log('\n=== Intersection (& пересечение) ===')
const record = createFullRecord(3, 'Иван', 'ivan@mail.ru')
console.log('Full record:', record)
console.log('Все поля:', Object.keys(record).join(', '))
console.log('\n=== Declaration merging (плагины) ===')
console.log('Плагины:', Object.keys(pluginRegistry).join(', '))
pluginRegistry.logger.log('Приложение запущено')
pluginRegistry.logger.warn('Память заканчивается')
console.log('Email валидный?', pluginRegistry.validator.email('test@example.com'))
console.log('Email пустой?', pluginRegistry.validator.required(''))
console.log('\n=== Конфликт типов при intersection ===')
// В TypeScript: type A = { x: string } & { x: number } → x: never
// В JS нет системы типов, но можно показать перезапись:
const a = { x: 'строка', y: 1 }
const b = { x: 42, z: true }
const merged = Object.assign({}, a, b)
console.log('После merge, x =', merged.x, typeof merged.x)
// В TypeScript это было бы ошибкой компиляции (never)На первый взгляд interface и type взаимозаменяемы:
// Оба описывают форму объекта
interface UserInterface {
id: number
name: string
}
type UserType = {
id: number
name: string
}
// Оба работают одинаково:
const u1: UserInterface = { id: 1, name: 'Алексей' }
const u2: UserType = { id: 1, name: 'Алексей' }Интерфейсы поддерживают слияние объявлений — несколько деклараций с одним именем объединяются автоматически:
interface Window {
myPlugin: () => void
}
// TypeScript добавит myPlugin к встроенному интерфейсу Window
interface Config {
host: string
}
interface Config {
port: number // добавляется к первому объявлению
}
// Итог: Config = { host: string; port: number }
// С type это невозможно:
type Config2 = { host: string }
// type Config2 = { port: number } // Ошибка: Duplicate identifier 'Config2'Declaration merging полезен при расширении сторонних библиотек.
Type aliases могут описывать то, что interface не может:
// Union type — только через type:
type StringOrNumber = string | number
type Status = 'active' | 'inactive' | 'pending'
// Пересечения через type:
type AdminUser = User & { permissions: string[] }
// Кортежи:
type Pair = [string, number]
// Primitive alias:
type UserId = string // interface не может алиасить примитив
// Conditional type:
type NonNullable<T> = T extends null | undefined ? never : TОба механизма расширяют тип, но с разным поведением при конфликте:
interface A { x: string }
interface B extends A { x: string; y: number } // OK — совместимый тип
type C = { x: string }
type D = C & { x: number } // Допускается, но x будет never (string & number)
// extends даёт ошибку при несовместимых типах — это лучше!
interface E { x: string }
// interface F extends E { x: number } // Ошибка TS — несовместимый тип обнаружен сразуКласс может реализовать interface или type-alias объекта:
interface Serializable {
serialize(): string
}
type HasId = { id: number }
class Document implements Serializable, HasId {
id = 0
serialize() { return JSON.stringify(this) }
}
// Оба работают с implementsИспользуй `interface`:
module augmentation)Используй `type`:
// Рекомендуемая практика:
interface User { // объект — interface
id: UserId
name: string
role: UserRole
}
type UserId = string // примитив — type
type UserRole = 'admin' | 'user' // union — type
type UserWithMeta = User & { // intersection — type
createdAt: Date
updatedAt: Date
}Паттерн расширения: базовый объект + дополнительные поля через Object.assign и spread — аналог interface extends и type intersection
// В TypeScript: interface extends и type intersection
// В JS — показываем runtime-аналоги этих паттернов
// --- Аналог interface extends ---
function createBase(id, name) {
return { id, name }
}
function createUser(id, name, email, role = 'user') {
// extends Base: добавляем поля поверх базы
return Object.assign(createBase(id, name), { email, role })
}
function createAdmin(id, name, email, permissions) {
// extends User: добавляем поля поверх User
return Object.assign(createUser(id, name, email, 'admin'), { permissions })
}
// --- Аналог type intersection (A & B) ---
function withTimestamps(obj) {
return Object.assign({}, obj, {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
}
function withSoftDelete(obj) {
return Object.assign({}, obj, {
deletedAt: null,
isDeleted: false,
})
}
// Intersection через spread: User & Timestamped & SoftDeletable
function createFullRecord(id, name, email) {
const base = createUser(id, name, email)
return withSoftDelete(withTimestamps(base))
}
// --- Declaration merging через Object.assign (расширение контракта) ---
// Базовый "интерфейс" для плагинов
const pluginRegistry = {}
function registerPlugin(name, plugin) {
pluginRegistry[name] = plugin
}
// Каждый модуль "расширяет" registry — аналог declaration merging
registerPlugin('logger', {
log: (msg) => console.log('[LOG]', msg),
warn: (msg) => console.log('[WARN]', msg),
})
registerPlugin('validator', {
required: (val) => val != null && val !== '',
email: (val) => /S+@S+.S+/.test(val),
})
// --- Демонстрация ---
console.log('=== Иерархия extends ===')
const user = createUser(1, 'Алексей', 'alex@mail.ru')
console.log('User:', user)
const admin = createAdmin(2, 'Ольга', 'olga@mail.ru', ['read', 'write', 'delete'])
console.log('Admin:', admin)
// Полиморфизм — оба имеют id и name:
const entities = [user, admin]
entities.forEach(e => console.log(` id=${e.id}, name=${e.name}, role=${e.role}`))
console.log('\n=== Intersection (& пересечение) ===')
const record = createFullRecord(3, 'Иван', 'ivan@mail.ru')
console.log('Full record:', record)
console.log('Все поля:', Object.keys(record).join(', '))
console.log('\n=== Declaration merging (плагины) ===')
console.log('Плагины:', Object.keys(pluginRegistry).join(', '))
pluginRegistry.logger.log('Приложение запущено')
pluginRegistry.logger.warn('Память заканчивается')
console.log('Email валидный?', pluginRegistry.validator.email('test@example.com'))
console.log('Email пустой?', pluginRegistry.validator.required(''))
console.log('\n=== Конфликт типов при intersection ===')
// В TypeScript: type A = { x: string } & { x: number } → x: never
// В JS нет системы типов, но можно показать перезапись:
const a = { x: 'строка', y: 1 }
const b = { x: 42, z: true }
const merged = Object.assign({}, a, b)
console.log('После merge, x =', merged.x, typeof merged.x)
// В TypeScript это было бы ошибкой компиляции (never)Реализуй три фабричные функции. `createProduct(id, name, price)` — возвращает объект продукта. `createDiscountedProduct(id, name, price, discountPercent)` — расширяет продукт (аналог interface extends) полями `discountPercent` и вычисленным `finalPrice` (price * (1 - discountPercent/100), округлить до 2 знаков через toFixed). `withStock(product, quantity)` — добавляет к любому продукту поля `quantity` и `inStock` (quantity > 0) не мутируя оригинал (аналог type intersection).
createDiscountedProduct: const base = createProduct(id, name, price); return { ...base, discountPercent, finalPrice: +(price * (1 - discountPercent/100)).toFixed(2) }. withStock: return { ...product, quantity, inStock: quantity > 0 } — spread создаёт новый объект.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке