- Описывать форму объекта через
interfaceиtype - Использовать необязательные (
?) и доступные только для чтения (readonly) свойства - Расширять интерфейс через
extends - Объяснять разницу между
interfaceиtype, а также структурную типизацию
В электронном журнале школы объект ученика проходит через десятки функций: его добавляют в список, считают оценки, выводят на экран. И каждый раз возникает один и тот же вопрос: какие свойства есть у этого объекта? grade или class? Всегда ли есть email? В TypeScript форму объекта описывают один раз и используют это описание везде, а компилятор следит, чтобы его соблюдали.
Описание формы объекта
Именованный тип, который описывает, какие свойства и каких типов должны быть у объекта. Объявляется ключевым словом interface и не создаёт никакого кода JavaScript — он нужен только для проверки.
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-поле.
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;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: объекту, объединению, кортежу и даже примитиву.
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-17ID может быть либо числом, либо строкой — такие объединения мы подробно разберём в следующем уроке.interface или type?
| Возможность | interface | type |
|---|---|---|
| Описать форму объекта | ✓ | ✓ |
| Расширить | extends | & (пересечение) |
| Дать имя объединению, кортежу или примитиву | ✗ | ✓ |
| Объявить повторно с тем же именем и слить | ✓ | ✗ |
interface с одинаковым именем автоматически сливаются (declaration merging), а два одноимённых type — ошибка.Структурная типизация
TypeScript сравнивает типы по структуре, а не по именам. Если у объекта есть нужные свойства, он подходит под тип — неважно, как он был создан. Ошибка о лишнем свойстве (TS2353) появляется только тогда, когда объект записан прямо как литерал: там лишнее свойство — скорее всего опечатка.
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.
email?: string в интерфейсе?