$ sudo teach IT
МОДУЛЬ 5 · УРОК 1

Что такое interface — описание структуры объектов

Синтаксис интерфейсов и их назначение в TypeScript

Цели урока

  • Понять, что такое интерфейс и зачем он нужен
  • Освоить синтаксис объявления интерфейсов
  • Научиться описывать структуру объектов
  • Понять разницу между интерфейсами и простыми типами
  • Изучить различные способы использования интерфейсов

Зачем нужны интерфейсы?

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

Интерфейс в TypeScript — это способ описать структуру объекта. Он определяет, какие свойства должен иметь объект, какие типы данных эти свойства могут содержать, и какие обязательные поля присутствуют. По сути, интерфейс — это контракт: «любой объект, соответствующий этому интерфейсу, гарантированно имеет такие-то свойства с такими-то типами».

Аналогия из реальной жизни:

Интерфейс — это как чертёж здания. Чертёж описывает, сколько комнат будет в доме, какого размера, где будут двери и окна. Любой строитель, работающий по этому чертежу, построит дом с одинаковой структурой. Интерфейс в TypeScript делает то же самое — он описывает «чертёж» объекта, которого должны придерживаться все, кто его использует.

Без интерфейсов в JavaScript типизация объектов была бы неудобной и трудоёмкой. Вам пришлось бы описывать тип каждого объекта заново везде, где он используется. Интерфейсы решают эту проблему, предоставляя единую точку определения структуры данных.

Кроме того, интерфейсы значительно улучшают читаемость кода. Когда другой разработчик видит функцию с параметром типа User, он сразу понимает, какие данные ожидает эта функция, даже не заглядывая в её реализацию.

Синтаксис объявления интерфейса

Объявление интерфейса начинается с ключевого слова interface, за которым следует имя интерфейса и тело в фигурных скобках:

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

В этом примере мы объявили интерфейс User, который описывает объект с тремя свойствами: name (строка), age (число) и email (строка).

Имя интерфейса должно начинаться с заглавной буквы (PascalCase). Это соглашение об именовании, которое помогает визуально отличать интерфейсы от переменных и функций. Например: User, Product, ApiResponse.

Каждое свойство интерфейса состоит из имени, двоеточия и типа данных. Свойства разделяются точкой с запятой или запятой. TypeScript рекомендует использовать точку с запятой для единообразия:

// Оба варианта допустимы:
interface UserA {
  name: string,    // через запятую
  age: number
}

interface UserB {
  name: string;    // через точку с запятой (рекомендуется)
  age: number;    // через точку с запятой (рекомендуется)
}

Использование интерфейса

После объявления интерфейса вы можете использовать его как тип для переменных, параметров функций и возвращаемых значений:

const user1: User = {
  name: "Алексей",
  age: 28,
  email: "alexey@example.com"
};

// TypeScript проверит, что объект соответствует интерфейсу
const user2: User = {
  name: "Мария",
  age: 24
  // Ошибка! Свойство email отсутствует
};

TypeScript компилятор автоматически проверит, что объект user2 не соответствует интерфейсу User, потому что в нём отсутствует обязательное свойство email. Это и есть основная сила интерфейсов — они позволяют ловить ошибки на этапе компиляции.

Также TypeScript проверит типы свойств. Если вы попытаетесь присвоить числовое значение свойству name, которое ожидает строку, компилятор выдаст ошибку:

const user3: User = {
  name: 123,     // Ошибка! Ожидается string
  age: "двадцать восемь",// Ошибка! Ожидается number
  email: "alexey@example.com"
};

Интерфейсы с методами

Интерфейсы могут описывать не только свойства, но и методы объекта. Методы описываются так же, как и функции, но без тела:

interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
  multiply(a: number, b: number): number;
}

const calc: Calculator = {
  add(a, b) { return a + b; },
  subtract(a, b) { return a - b; },
  multiply(a, b) { return a * b; }
};

// calc.divide(5, 2); — ошибка: метод divide не определён в интерфейсе

Обратите внимание: интерфейс описывает сигнатуру метода — имена параметров и их типы, а также тип возвращаемого значения. Реализация метода определяется в объекте, который соответствует интерфейсу. Если вы добавите метод, которого нет в интерфейсе, TypeScript предупредит об этом.

Интерфейсы также могут описывать методы без параметров, методы с необязательными параметрами и методы, которые ничего не возвращают (тип void):

interface Logger {
  log(message: string): void;          // ничего не возвращает
  warn(message: string): void;
  error(message: string): void;
  clear(): void;                     // без параметров
}

Интерфейсы для параметров функций

Одно из самых частых применений интерфейсов — типизация параметров функций. Это делает код более читаемым и документированным:

interface Product {
  name: string;
  price: number;
  category: string;
}

function getDiscountedPrice(product: Product, discount: number): number {
  return product.price * (1 - discount);
}

function formatProduct(product: Product): string {
  return `${product.name} — ${product.price} руб. (${product.category})`;
}

function isExpensive(product: Product, threshold: number): boolean {
  return product.price > threshold;
}

Вместо того чтобы передавать объект с неизвестной структурой, мы чётко определяем, что функция ожидает. Это особенно ценно при работе в команде — другие разработчики сразу видят, какие данные нужны функции.

Интерфейсы также позволяют описывать функции, которые принимают объекты с разной структурой, но с общими свойствами. Это особенно полезно при работе с API-ответами:

interface ApiResponse {
  status: number;
  message: string;
  data: unknown;
}

function handleResponse(response: ApiResponse): void {
  if (response.status === 200) {
    console.log(`Успех: ${response.message}`);
  } else {
    console.error(`Ошибка ${response.status}: ${response.message}`);
  }
}

Именование интерфейсов

В TypeScript есть общепринятые соглашения об именовании интерфейсов:

Правильно:
  • interface User
  • interface Product
  • interface UserProfile
  • interface ApiResponse
  • interface DatabaseConfig
Неправильно:
  • interface iUser
  • interface IUser
  • interface user_interface
  • interface UserProps
  • interface user

Интерфейсы именуются в PascalCase (с заглавной буквы) без префиксов I или i. Это стандарт сообщества TypeScript, рекомендованный в официальной документации.

Исключение составляют случаи, когда интерфейс описывает опции или пропсы компонента — тогда добавление суффикса Options или Props допустимо:

// Допустимые имена для конфигураций и пропсов:
interface ServerOptions { ... }
interface ButtonProps { ... }
interface ModalConfig { ... }

// Но для основных сущностей — просто PascalCase:
interface User { ... }
interface Product { ... }
interface Order { ... }

Интерфейсы для возвращаемых значений

Интерфейсы также удобно использовать для типизации возвращаемых значений функций:

interface WeatherData {
  temperature: number;
  humidity: number;
  windSpeed: number;
  description: string;
}

// Функция возвращает объект типа WeatherData
// TypeScript знает структуру результата
// Вы получите полную подсказку при использовании

interface CacheEntry<T> {
  value: T;
  expiresAt: Date;
  key: string;
}

Когда функция возвращает объект, описанный интерфейсом, TypeScript автоматически выведет тип возвращаемого значения. Вы получите полную подсказку типов при использовании результата.

Переиспользование интерфейсов

Одно из главных преимуществ интерфейсов — возможность использовать один и тот же интерфейс в разных местах программы:

// Один интерфейс — множество применений
interface Point {
  x: number;
  y: number;
}

// 1. В функции для вычисления расстояния
function getDistance(p1: Point, p2: Point): number {
  return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}

// 2. В классе
class Circle {
  center: Point;
  radius: number;
  constructor(center: Point, radius: number) {
    this.center = center;
    this.radius = radius;
  }
}

// 3. В массиве
const polygon: Point[] = [
  { x: 0, y: 0 },
  { x: 10, y: 0 },
  { x: 5, y: 8 }
];

// 4. В объекте-конфигурации
const viewport = {
  topLeft: { x: 0, y: 0 } as Point,
  bottomRight: { x: 100, y: 100 } as Point
};

Как видите, интерфейс Point переиспользуется в функции, классе и массиве. Изменение интерфейса в одном месте автоматически отразится на всех местах использования.

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

Рассмотрим реальный пример использования интерфейсов для работы с REST API:

// Описываем структуру данных, получаемых от API
interface Post {
  id: number;
  title: string;
  body: string;
  userId: number;
}

// Функция для получения постов
// TypeScript автоматически выведит тип возвращаемого значения
async function fetchPosts(): Promise<Post[]> {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  return response.json();
}

// Использование — полная подсказка типов!
const posts = await fetchPosts();
posts.forEach(post => {
  console.log(post.title); // TypeScript знает, что title — string
  console.log(post.id);     // TypeScript знает, что id — number
});

Использование интерфейсов для описания структуры API-ответов делает код самодокументируемым. Любой разработчик сразу видит, какие данные возвращает функция.

Интерфейсы и структурная типизация

В TypeScript используется структурная типизация (also known as duck typing). Это означает, что объект соответствует интерфейсу, если он имеет все необходимые свойства с правильными типами — даже если явно не объявлен как соответствующий этому интерфейсу:

interface Point {
  x: number;
  y: number;
}

// Этот объект не объявлен как Point, но структурно ему соответствует
const myObj = { x: 10, y: 20, z: 30 };

// Это работает, потому что myObj имеет x и y типа number
// Дополнительное свойство z не мешает
const point: Point = myObj; // OK!

// Функция также принимает любой объект с x и y
function printPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

printPoint(myObj);     // OK! Выведет "10, 20"
printPoint({ x: 1, y: 2 }); // OK! Inline объект
Важно:

При прямом присваивании объекта интерфейсу (не через переменную) TypeScript проверяет отсутствие лишних свойств. Это называется «проверка лишних свойств» (excess property checking). Подробнее об этом мы поговорим в уроке о type aliases.

Интерфейсы с вложенными объектами

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

interface Address {
  street: string;
  city: string;
  country: string;
}

interface Company {
  name: string;
  address: Address;
}

interface Employee {
  name: string;
  company: Company;
}

const emp: Employee = {
  name: "Алексей",
  company: {
    name: "TechCorp",
    address: {
      street: "ул. Примерная, д. 1",
      city: "Москва",
      country: "Россия"
    }
  }
};

Вложенные интерфейсы позволяют описывать сложные иерархические структуры данных, что особенно полезно при работе с API-ответами и конфигурациями.

Интерфейсы vs объявления типов

Иногда интерфейсы можно спутать с другими способами описания типов. Вот краткое сравнение:

// Интерфейс
interface User {
  name: string;
  age: number;
}

// Type alias (мы изучим подробнее в следующем уроке)
type UserType = {
  name: string;
  age: number;
};

// Оба варианта работают одинаково для простых случаев:
const userA: User = { name: "A", age: 25 };
const userB: UserType = { name: "B", age: 30 };

Для простых случаев оба варианта работают одинаково. Однако интерфейсы имеют некоторые уникальные возможности, которые мы рассмотрим в уроке 5_6.

Практический пример: интернет-магазин

Рассмотрим более сложный пример — описание структуры данных для интернет-магазина:

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
}

interface CartItem {
  product: Product;
  quantity: number;
}

interface Order {
  id: number;
  items: CartItem[];
  total: number;
  createdAt: Date;
}

// Функции для работы с заказами
function calculateTotal(items: CartItem[]): number {
  return items.reduce((sum, item) => sum + item.product.price * item.quantity, 0);
}

function getProductsByCategory(products: Product[], category: string): Product[] {
  return products.filter(p => p.category === category);
}

Интерфейсы создают чёткую структуру данных, которая легко расширяется и поддерживается. Все функции работают с предсказуемыми типами данных.

Практическое задание

Создайте интерфейс Book для описания книги с полями: title, author, year, isbn, pages. Затем создайте интерфейс Library, который содержит массив книг и методы для поиска:

// 1. Создайте интерфейс Book
// 2. Создайте интерфейс Library с методами:
// - getBookByIsbn(isbn: string): Book | undefined
// - getBooksByAuthor(author: string): Book[]
// - addBook(book: Book): void
// 3. Создайте объект library и протестируйте методы

Чек-лист урока

  • Interface — это способ описать структуру объекта
  • Синтаксис: interface Name { ... }
  • Интерфейсы описывают свойства и методы объектов
  • TypeScript проверяет соответствие объектов интерфейсам
  • Интерфейсы можно использовать как типы для переменных, параметров и возвращаемых значений
  • Интерфейсы именуются в PascalCase без префиксов
  • TypeScript использует структурную типизацию — объект соответствует интерфейсу, если имеет нужные свойства
  • Интерфейсы могут содержать вложенные объекты и описывать сложные структуры данных

Тест по интерфейсам

8 вопросов

Приветствие пользователя

Premium