Расширение интерфейсов: extends

Ключевое слово extends в интерфейсах TypeScript создаёт новый интерфейс, наследующий все свойства родителя и добавляющий новые, что позволяет строить иерархию типов без дублирования полей.

Зачем нужно

extends позволяет строить типы от общего к частному: базовый AnimalPetDog, или BaseEntityUser, Product, Order. Это исключает дублирование общих полей (id, createdAt) и устанавливает явную иерархию.

Где используется

  • Иерархия доменных моделей: BaseEntity → User, Product
  • Расширение DTO: CreateUserDto → UpdateUserDto
  • Библиотечные типы: расширение встроенных интерфейсов (Window, Request)
  • Миксин-паттерны через множественный extends

Основной контент

Базовый синтаксис

interface Animal {
  name: string;
  age: number;
}

interface Pet extends Animal {
  owner: string;
}

interface Dog extends Pet {
  breed: string;
}

// Dog содержит все поля: name, age, owner, breed
const dog: Dog = {
  name: "Rex",
  age: 3,
  owner: "Alice",
  breed: "Labrador",
};

Множественный extends

interface Serializable {
  serialize: string;
}

interface Persistable {
  save: Promise<void>;
  delete: Promise<void>;
}

interface HasId {
  id: string;
}

// Расширяем несколько интерфейсов сразу
interface Entity extends HasId, Serializable, Persistable {
  createdAt: Date;
  updatedAt: Date;
}

Паттерн BaseEntity

interface BaseEntity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

interface User extends BaseEntity {
  name: string;
  email: string;
  role: "admin" | "user";
}

interface Product extends BaseEntity {
  title: string;
  price: number;
  stock: number;
}

// Функция работает с любой entity
function getById<T extends BaseEntity>(
  items: T,
  id: string
): T | undefined {
  return items.find(item => item.id === id);
}

Переопределение свойств

interface Base {
  value: string | number;
  label?: string;
}

// Можно сузить тип свойства (но не расширить)
interface Specialized extends Base {
  value: string; // OK — string совместим с string | number
  label: string; // OK — убираем опциональность (делаем строже)
}

// Нельзя расширить несовместимым типом:
// interface Wrong extends Base {
//   value: boolean; // Error — boolean не совместим с string | number
// }

extends vs &

// Два способа комбинировать типы:
interface C1 extends A, B { extra: string }
type C2 = A & B & { extra: string };

// Ключевое различие:
// extends проверяет совместимость при наследовании
// & молча создаёт never при несовместимых полях

interface A2 { value: string }
interface B2 { value: number }
// interface C3 extends A2, B2 {} // Error: Types of property 'value' are incompatible
type C3 = A2 & B2; // { value: string & number } = { value: never }

Declaration merging как неявный extends

// Два объявления одного interface объединяются
interface Config { host: string }
interface Config { port: number }
// Итог: { host: string; port: number }

// Используется в Module Augmentation:
declare module "express" {
  interface Request {
    user?: User;
  }
}

Частые ошибки

  • Несовместимое переопределение свойства — нельзя расширить value: string до value: string | number; только сузить.
  • Circular extendsinterface A extends B {} и interface B extends A {} — ошибка циклической зависимости.
  • Путать interface extends и class extends — interface extends только копирует контракт; class extends наследует реализацию.
  • Думать что extends в interface = implements — interface extends создаёт тип; класс должен явно писать implements.

Связанные темы

Ресурсы