Перейти к содержанию
Educora
Средний16 мин4 / 8

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

Описывай форму объектов с помощью `interface` и `type`, используй необязательные и `readonly`-свойства, расширяй интерфейсы и узнай, когда что выбирать.

Проверь себя
В этом уроке ты узнаешь
  • Описывать форму объекта через interface и type
  • Использовать необязательные (?) и доступные только для чтения (readonly) свойства
  • Расширять интерфейс через extends
  • Объяснять разницу между interface и type, а также структурную типизацию

В электронном журнале школы объект ученика проходит через десятки функций: его добавляют в список, считают оценки, выводят на экран. И каждый раз возникает один и тот же вопрос: какие свойства есть у этого объекта? grade или class? Всегда ли есть email? В TypeScript форму объекта описывают один раз и используют это описание везде, а компилятор следит, чтобы его соблюдали.

Описание формы объекта

Определение
Интерфейс

Именованный тип, который описывает, какие свойства и каких типов должны быть у объекта. Объявляется ключевым словом interface и не создаёт никакого кода JavaScript — он нужен только для проверки.

TypeScript
interface Student {
  readonly id: number;
  name: string;
  grade: number;
  email?: string;
}

const aysel: Student = { id: 1, name: 'Aysel', grade: 9 };
const murad: Student = { id: 2, name: 'Murad', grade: 10, email: 'murad@example.com' };

function describe(s: Student): string {
  const contact = s.email ?? 'no email';
  return `#${s.id} ${s.name}, grade ${s.grade} (${contact})`;
}

console.log(describe(aysel));
console.log(describe(murad));
Ожидаемый результат
#1 Aysel, grade 9 (no email)
#2 Murad, grade 10 (murad@example.com)
  • email?: string — необязательное свойство: его может не быть в объекте, а при чтении его тип — string | undefined. Поэтому мы подставили запасное значение через ??.
  • readonly id: number — свойство только для чтения: оно задаётся при создании объекта, и изменить его потом нельзя.
  • Свойства разделяют точкой с запятой или запятой; их порядок не важен.

А теперь попробуем нарушить договор: забудем свойство, добавим лишнее и изменим readonly-поле.

TypeScript
interface Student {
  readonly id: number;
  name: string;
  grade: number;
}

const leyla: Student = { id: 3, name: 'Leyla' };
const elvin: Student = { id: 4, name: 'Elvin', grade: 8, age: 14 };
elvin.id = 5;
Text
students.ts:7:7 - error TS2741: Property 'grade' is missing in type '{ id: number; name: string; }' but required in type 'Student'.

7 const leyla: Student = { id: 3, name: 'Leyla' };
        ~~~~~

  students.ts:4:3
    4   grade: number;
        ~~~~~
    'grade' is declared here.

students.ts:8:58 - error TS2353: Object literal may only specify known properties, and 'age' does not exist in type 'Student'.

8 const elvin: Student = { id: 4, name: 'Elvin', grade: 8, age: 14 };
                                                           ~~~

students.ts:9:7 - error TS2540: Cannot assign to 'id' because it is a read-only property.

9 elvin.id = 5;
        ~~
Три ошибки — три разных кода: недостающее свойство (TS2741), лишнее свойство (TS2353) и присваивание readonly-полю (TS2540).

Расширение и псевдонимы типов

Интерфейс может расширять другой интерфейс через extends — как наследование у классов: Teacher получает все свойства Person и добавляет своё. А псевдоним типа (type alias) даёт имя любому типу с помощью ключевого слова type: объекту, объединению, кортежу и даже примитиву.

TypeScript
interface Person {
  name: string;
}

interface Teacher extends Person {
  subject: string;
}

type Point = { x: number; y: number };
type ID = number | string;

const teacher: Teacher = { name: 'Elvin', subject: 'Physics' };
const start: Point = { x: 0, y: 0 };
const code: ID = 'A-17';

console.log(`${teacher.name} teaches ${teacher.subject}`);
console.log(start, code);
Ожидаемый результат
Elvin teaches Physics
{ x: 0, y: 0 } A-17
ID может быть либо числом, либо строкой — такие объединения мы подробно разберём в следующем уроке.

interface или type?

Возможностьinterfacetype
Описать форму объекта✓✓
Расширитьextends& (пересечение)
Дать имя объединению, кортежу или примитиву✗✓
Объявить повторно с тем же именем и слить✓✗
Два interface с одинаковым именем автоматически сливаются (declaration merging), а два одноимённых type — ошибка.

Структурная типизация

TypeScript сравнивает типы по структуре, а не по именам. Если у объекта есть нужные свойства, он подходит под тип — неважно, как он был создан. Ошибка о лишнем свойстве (TS2353) появляется только тогда, когда объект записан прямо как литерал: там лишнее свойство — скорее всего опечатка.

TypeScript
interface Named {
  name: string;
}

function hello(thing: Named): string {
  return `Hello, ${thing.name}!`;
}

const dog = { name: 'Rex', legs: 4 };
console.log(hello(dog));
console.log(hello({ name: 'Baku' }));
Ожидаемый результат
Hello, Rex!
Hello, Baku!
dog никогда не объявлялся как Named и имеет лишнее свойство legs, но подходит, потому что у него есть name.

Главное

  • interface и type описывают, какие свойства и типы есть у объекта; кода JavaScript они не создают.
  • prop?: T — необязательное свойство, а readonly prop: T нельзя изменить после создания.
  • Интерфейс расширяется через extends; type может дать имя любому типу, в том числе объединению.
  • Компилятор ловит недостающие свойства (TS2741) и лишние свойства в литералах (TS2353).
  • TypeScript использует структурную типизацию: если форма подходит, имя не важно.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что означает запись email?: string в интерфейсе?