ДокументацияTypeScriptИнтерфейсы и type aliases в TypeScript
Начальный 10 мин чтения

Интерфейсы и type aliases в TypeScript

Интерфейсы (interface) и псевдонимы типов (type aliases) — два способа описания форм объектов в TypeScript. Разбираемся, чем они отличаются и когда что использовать.

typescriptinterfacetypegenericsextends

Что такое интерфейс

Интерфейс описывает форму объекта — какие у него должны быть свойства и какого типа:

interface User {
  name: string
  age: number
  email: string
}

const user: User = {
  name: 'Анна',
  age: 25,
  email: 'anna@example.com',
}

Если забыть поле или добавить лишнее — TypeScript укажет на ошибку.

Опциональные и readonly-свойства

interface Article {
  title: string
  content: string
  tags?: string[]          // необязательное свойство
  readonly createdAt: Date // нельзя изменить после создания
}

const post: Article = {
  title: 'TypeScript',
  content: 'Текст статьи',
  createdAt: new Date(),
}

post.title = 'Новое название' // ok
post.createdAt = new Date()   // Ошибка: readonly

Что такое type alias

Type alias (псевдоним типа) — это имя для любого типа, не только объекта:

type ID = string | number
type Callback = (data: string) => void
type Coordinates = [number, number]

Для объектов синтаксис почти такой же, как у интерфейса:

type User = {
  name: string
  age: number
  email: string
}

Интерфейсы vs type aliases

Большую часть времени можно использовать любой из вариантов. Но есть отличия:

Наследование

Интерфейсы расширяются через extends:

interface Animal {
  name: string
}

interface Dog extends Animal {
  breed: string
}

const rex: Dog = { name: 'Rex', breed: 'Овчарка' }

Type alias — через & (intersection):

type Animal = {
  name: string
}

type Dog = Animal & {
  breed: string
}

Объединение (Union)

Только type поддерживает union:

type Status = 'active' | 'inactive'
type Result = Success | Error

Интерфейс так не умеет.

Declaration merging

Интерфейсы с одинаковым именем автоматически объединяются:

interface Config {
  host: string
}

interface Config {
  port: number
}

const config: Config = { host: 'localhost', port: 3000 }

Для type alias так нельзя — будет ошибка «Duplicate identifier». Это свойство полезно при написании_augmentations_ — расширении сторонних типов.

Когда что использовать

СитуацияВыбор
Форма объекта, который может расширятьсяinterface
Union, tuple, примитивtype
Mapped types, conditional typestype
Расширение типов библиотекиinterface
Всё остальноеНа ваш вкус

Многие команды выбирают одно правило: интерфейсы для объектов, type aliases для всего остального. Другие используют только type. Оба подхода рабочие.

Индексные сигнатуры

Когда вы не знаете заранее имена свойств, но знаете тип значений:

interface Dictionary {
  [key: string]: string
}

const translations: Dictionary = {
  hello: 'Привет',
  goodbye: 'До свидания',
  thanks: 'Спасибо',
}

С числовыми ключами:

interface NumberMap {
  [key: number]: string
}

const errors: NumberMap = {
  404: 'Не найдено',
  500: 'Ошибка сервера',
}

Вложенные типы

Объекты часто содержат другие объекты. Типы можно описывать прямо внутри или выносить:

interface User {
  name: string
  address: {
    city: string
    street: string
    zip: string
  }
}

Или вынести для переиспользования:

interface Address {
  city: string
  street: string
  zip: string
}

interface User {
  name: string
  address: Address
}

Второй вариант лучше — Address можно переиспользовать.

Методы в интерфейсах

Три способа объявить метод:

interface Logger {
  log(message: string): void
  warn(message: string): void
  error(message: string): void
}

// Альтернатива через свойство-функцию
interface Logger2 {
  log: (message: string) => void
  warn: (message: string) => void
}

Разница появляется только при перегрузках — интерфейсный синтаксис гибче.

Расширение типов библиотек (Module Augmentation)

Когда нужно добавить поле к существующему интерфейсу из библиотеки:

import type { RouteMeta } from 'vue-router'

declare module 'vue-router' {
  interface RouteMeta {
    requiresAuth?: boolean
    title?: string
  }
}

Теперь route.meta.requiresAuth будет доступен во всём проекте. Это работает именно благодаря declaration merging интерфейсов.

Практические примеры

Типизация компонента Vue:

interface Props {
  title: string
  count?: number
  variant: 'primary' | 'secondary'
}

interface Emits {
  (e: 'update', value: number): void
  (e: 'delete'): void
}

Типизация API-ответа:

interface PaginatedResponse<T> {
  data: T[]
  total: number
  page: number
  perPage: number
}

type UserListResponse = PaginatedResponse<{
  id: number
  name: string
  email: string
}>

Конфигурация с дефолтными значениями:

interface FetchOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
  headers?: Record<string, string>
  body?: string
  timeout?: number
}

function fetchWithDefaults(url: string, options: FetchOptions = {}) {
  const {
    method = 'GET',
    headers = {},
    timeout = 5000,
  } = options

  // ...
}

Итог

Интерфейсы и type aliases — два способа описать форму данных. Для объектов оба работают, но интерфейсы поддерживают extends и declaration merging, а type aliases — union, intersection и продвинутые типы. Выбирайте то, что удобнее в конкретной ситуации, но будьте последовательны в рамках проекта.