Interface и type в TypeScript: когда что использовать

В TypeScript почти сразу возникает вопрос: объект описывать через interface или через type? Новичок открывает чужой код и видит оба варианта рядом. Один разработчик пишет interface User, другой пишет type User = { ... }, третий говорит, что "все равно", а четвертый начинает спорить про расширение и declaration merging

Для первого рабочего кода не нужно превращать это в религию. Нам важно понять, что оба инструмента могут описывать форму объекта, но type умеет шире: union, примитивы, функции, кортежи и другие комбинации. interface чаще берут для объектов и публичных контрактов

В этом уроке разберем на примере учебной платформы: урок, автор, статус публикации и функция вывода карточки

Что получится в конце

Мы опишем объект урока двумя способами:

interface Author {
  id: number;
  name: string;
}

type LessonStatus = "draft" | "published" | "archived";

interface Lesson {
  id: number;
  title: string;
  minutes: number;
  author: Author;
  status: LessonStatus;
}

И сделаем функцию:

function printLesson(lesson: Lesson): string {
  return `${lesson.title} - ${lesson.author.name}`;
}

Здесь interface хорошо описывает объект, а type хорошо описывает набор разрешенных строк

Один объект без имени

TypeScript позволяет описать объект прямо в параметре:

function printUser(user: { name: string; age: number }): string {
  return `${user.name}, ${user.age}`;
}

Для очень маленького примера это нормально. Но если объект нужен в нескольких местах, такая запись быстро надоедает. Лучше дать форме имя:

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

function printUser(user: User): string {
  return `${user.name}, ${user.age}`;
}

Теперь User можно использовать в разных функциях, компонентах, ответах API и тестах

То же самое через type

Через type объект выглядит почти так же:

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

function printUser(user: User): string {
  return `${user.name}, ${user.age}`;
}

На уровне обычной формы объекта оба варианта читаются спокойно. Если твоя команда уже выбрала стиль, лучше придерживаться его. Если стиля нет, можно держать простое правило: для объектов чаще interface, для комбинаций типов чаще type

Optional properties

Поле может быть необязательным. Например, у урока может быть описание, а может не быть:

interface Lesson {
  title: string;
  minutes: number;
  description?: string;
}

Знак ? означает, что поле может отсутствовать

Но это не значит, что с ним можно обращаться как с обычной строкой:

function printDescription(lesson: Lesson): string {
  return lesson.description.toUpperCase();
}

TypeScript справедливо ругнется: description может быть undefined. Нужно проверить:

function printDescription(lesson: Lesson): string {
  if (!lesson.description) {
    return "Описание пока не добавлено";
  }

  return lesson.description.toUpperCase();
}

Это хороший пример пользы типов. Ошибка не в синтаксисе, а в логике: мы забыли, что поле необязательное

Расширение interface

interface удобно расширять:

interface LessonBase {
  id: number;
  title: string;
}

interface PublishedLesson extends LessonBase {
  publishedAt: string;
  views: number;
}

Теперь PublishedLesson содержит id, title, publishedAt, views

Это удобно, когда есть базовая форма и несколько уточнений. Например, черновик урока, опубликованный урок и архивный урок

Intersection через type

Через type похожая задача решается пересечением:

type LessonBase = {
  id: number;
  title: string;
};

type SeoFields = {
  slug: string;
  description: string;
};

type SeoLesson = LessonBase & SeoFields;

SeoLesson получает поля из обоих типов. Для новичка это можно читать как "и то, и другое"

Такой подход удобен, когда тип складывается из нескольких независимых частей

Где type сильнее

type может описывать не только объект:

type ID = number | string;
type Status = "draft" | "published" | "archived";
type ClickHandler = (id: ID) => void;

interface так не пишет. Поэтому для union types и function aliases обычно берут type

Практический пример:

type LessonStatus = "draft" | "published" | "archived";

interface Lesson {
  title: string;
  status: LessonStatus;
}

Здесь нет конфликта. type и interface работают вместе

Спокойное правило выбора

Если описываешь обычный объект, который может расширяться, начни с interface:

interface Product {
  id: number;
  title: string;
  price: number;
}

Если описываешь union, примитивный alias, функцию или комбинацию, бери type:

type ProductStatus = "active" | "hidden";
type ProductId = number | string;
type ProductFormatter = (product: Product) => string;

Если в проекте уже принят другой стиль, важнее единообразие. TypeScript не ломается от того, что объект описан через type

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

Первая ошибка — спорить о выборе interface и type раньше, чем появился рабочий код. Для первых уроков важнее понять форму объекта

Вторая ошибка — писать все через any, чтобы не описывать объект. Тогда TypeScript перестает защищать от опечаток в полях

Третья ошибка — забывать проверять optional-поля. Если поле помечено ?, с ним нужно обращаться как с потенциально отсутствующим

Четвертая ошибка — делать слишком много маленьких типов без необходимости. Если тип используется один раз и не повышает читаемость, возможно, он пока не нужен

Мини-задание

Опиши учебный курс:

type CourseStatus = "draft" | "published";

interface CourseAuthor {
  id: number;
  name: string;
}

interface Course {
  title: string;
  status: CourseStatus;
  author: CourseAuthor;
  description?: string;
}

Потом напиши функцию:

function renderCourse(course: Course): string {
  return `${course.title} - ${course.author.name}`;
}

Проверь, что TypeScript подсветит ошибку, если в status передать "deleted"

Что может быть еще интересно по этой теме

Можно ли всегда использовать только type?

Можно, многие проекты так делают. Но для объектов interface часто читается привычнее, особенно когда есть расширение через extends

Можно ли всегда использовать только interface?

Нет, потому что interface не заменяет union types и удобные aliases для функций или примитивов

Что лучше для React props?

Оба варианта встречаются. Если props — обычный объект, можно использовать interface. Если props завязаны на union или сложную композицию, удобнее type

Нужно ли переименовывать все типы в проекте?

Нет. Единый стиль важен, но массовая механическая замена редко дает пользу сама по себе

Что почитать дальше по TypeScript

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x