В прошлом уроке типы объектов получались длинными, и их приходилось повторять:
function formatUser(user: { name: string; age: number; city?: string }): string { ... }
function isAdult(user: { name: string; age: number; city?: string }): boolean { ... }Поменяли поле — правьте везде. Забыли одно место — типы разъехались. Как и со значениями, повторяющийся тип хочется вынести и назвать по имени.
type: имя для типа
type даёт типу имя — псевдоним:
type User = {
name: string
age: number
city?: string
}
function formatUser(user: User): string { ... }
function isAdult(user: User): boolean { ... }
const users: User[] = []Имена типов принято писать с большой буквы: User, Product, Order.
Так их легко отличить от переменных. Внутри фигурных скобок поля можно
разделять переносом строки — точка с запятой не обязательна.
Псевдоним можно дать любому типу, не только объекту:
type Point = [number, number]
type Scores = number[]
type UserId = numberВажно: type не создаёт новый тип, а лишь называет существующий. User и
{ name: string; age: number; city?: string } — для компилятора одно и то
же. И, как все типы, type при запуске стирается.
interface: описание формы объекта
Второй способ назвать тип объекта — interface:
interface User {
name: string
age: number
city?: string
}Отличия от type на вид небольшие: нет знака =, и описать так можно
только объект. Всё остальное — ?, readonly, массивы User[] —
работает одинаково.
extends: один тип на основе другого
Часто один тип — это другой плюс несколько полей. Админ — это пользователь,
у которого ещё есть права. Чтобы не переписывать общие поля, у
интерфейсов есть extends:
interface User {
id: number
name: string
}
interface Admin extends User {
permissions: string[]
}
const admin: Admin = { id: 1, name: 'Аня', permissions: ['delete'] }У Admin три поля: два унаследованы от User, одно своё. И раз у админа
есть всё, что есть у пользователя, админа можно передать туда, где ждут
пользователя:
function greet(user: User): string {
return 'Привет, ' + user.name
}
greet(admin) // можно: Admin подходит под UserTypeScript проверяет типы по форме: подходит любой объект, у которого есть нужные поля нужных типов. Как он назван — неважно.
&: склеить два типа
У type вместо extends есть пересечение — знак &. A & B — это
объект, в котором есть и всё из A, и всё из B:
type WithId = { id: number }
type Product = { title: string; price: number }
type SavedProduct = Product & WithId
const saved: SavedProduct = { id: 7, title: 'Чайник', price: 2500 }Удобно, когда у разных сущностей есть общий кусок: WithId можно приклеить
и к товару, и к заказу, и к пользователю.
Лишние поля в литерале
Раз проверка идёт по форме, лишнее поле вроде бы не мешает: у объекта есть всё нужное. Так и есть — но с одним исключением. Если объект написан прямо на месте, в фигурных скобках, лишнее поле — ошибка:
const user: User = { id: 1, name: 'Аня', emial: 'a@b.ru' }
// ошибка: Object literal may only specify known properties,
// and 'emial' does not exist in type 'User'Логика простая: раз вы пишете объект именно для User, поле, которого в
User нет, — скорее всего опечатка. Здесь emial вместо email, и без
этой проверки поле молча потерялось бы.
А вот объект, который уже лежит в переменной, проверяется только по форме:
const admin = { id: 1, name: 'Аня', permissions: ['delete'] }
const user: User = admin // можно: всё нужное есть, лишнее не мешаетЕсли на литерал ругается «Object literal may only specify known properties», — проверьте имя поля или добавьте поле в тип.
Что выбрать
На практике оба способа подходят для объектов, и в реальных проектах встречаются оба. Простое правило:
- форма объекта —
interface, особенно если нуженextends; - всё остальное — массивы, кортежи, склейки через
&, а в следующем модуле объединения —type, потому чтоinterfaceтак не умеет.
Если в проекте уже принято иначе — следуйте проекту. Главное — давать повторяющимся типам имена и не переписывать одни и те же поля по десять раз.
Что запомнить
type Имя = ...— имя для любого типа.interface Имя { ... }— имя для формы объекта.interface B extends A— всё изAплюс свои поля.A & B— тип, в котором есть поля иA, иB.- Лишнее поле в литерале
{ ... }— ошибка: TypeScript считает его опечаткой.
Первые типы пройдены
Это был последний бесплатный урок. Посмотрите, что вы уже умеете: подписывать переменные и массивы типами, описывать объекты с необязательными и только читаемыми полями, читать ошибки компилятора, давать типам имена через type и interface и собирать один тип из другого.
Дальше типы начинают работать по-настоящему:
- Объединения и сужение. Тип «или то, или это», проверки через
typeofиin, размеченные объединения. - Функции и дженерики. Типы параметров и колбэков, дженерики и обобщённые типы.
- Утилиты и классы.
keyof,typeof,Partial,Pick,Omit,Recordи классы с модификаторами доступа. - Строгий режим и проект.
unknown, свои проверки типов,tsconfig.jsonи проект «прогноз погоды без any».
Остальные модули открываются после покупки курса: один платёж, доступ навсегда. Купить можно на странице курса.