В прошлом уроке типы были простые: строка, число, логическое значение. Но настоящие данные редко бывают одним числом. Это списки оценок, карточки пользователей, корзины с товарами — массивы и объекты. Сейчас научимся описывать и их.
Массивы: тип элемента и квадратные скобки
Тип массива пишут как тип элемента и [] после него:
const scores: number[] = [5, 4, 5]
const names: string[] = ['Аня', 'Боря']
scores.push(3) // можно: 3 — число
scores.push('пять') // ошибка: Argument of type 'string' is not assignable to parameter of type 'number'number[] читается «массив чисел». Компилятор следит, чтобы туда попадали
только числа, и знает, что достаёт оттуда тоже числа:
const first = scores[0] // number
const upper = names.map(name => name.toUpperCase()) // string[]В map тип name писать не пришлось: компилятор знает, что в names лежат
строки. Это тот же вывод типов, что и с переменными.
Есть и вторая запись — Array<number>. Это ровно тот же тип, просто
длиннее. В коде чаще встречается number[], но если увидите Array<...>,
не пугайтесь: угловые скобки подробно разберём в уроке про дженерики.
Объекты: какие поля и какого они типа
Тип объекта описывают фигурными скобками: имя поля, двоеточие, тип. Поля разделяют точкой с запятой (или запятой — тоже можно):
const user: { name: string; age: number } = {
name: 'Аня',
age: 25,
}
user.age = 26 // можно
user.age = '26' // ошибка: Type 'string' is not assignable to type 'number'
user.email // ошибка: Property 'email' does not exist on type '{ name: string; age: number; }'Последняя ошибка особенно полезна: опечатку в имени поля JavaScript
пропустил бы молча и вернул undefined, а TypeScript ловит её сразу.
Так же описывают параметр функции:
function greet(user: { name: string; age: number }): string {
return `Привет, ${user.name}! Тебе ${user.age}`
}
greet({ name: 'Аня', age: 25 }) // 'Привет, Аня! Тебе 25'
greet({ name: 'Боря' }) // ошибка: Property 'age' is missingМассив объектов
Тип объекта — такой же тип, как number. Значит, к нему тоже можно
приписать []:
const users: { name: string; age: number }[] = [
{ name: 'Аня', age: 25 },
{ name: 'Боря', age: 17 },
]
const adults = users.filter(user => user.age >= 18) // { name: string; age: number }[]Длинно? Да. В следующем уроке дадим такому типу короткое имя. А пока главное — понять, как тип собирается из частей.
Необязательные поля: знак вопроса
Бывает, что поле есть не всегда. Город пользователь мог не указать. Такое
поле помечают ? перед двоеточием:
function describe(user: { name: string; city?: string }): string {
return user.name
}
describe({ name: 'Аня', city: 'Казань' }) // можно
describe({ name: 'Боря' }) // тоже можно: city необязателенНо у вопроса есть цена. Раз поля может не быть, при чтении его тип —
string | undefined: «строка или ничего». Вертикальная черта значит «или»,
подробно о ней — в следующем модуле. Пока важно другое: компилятор не даст
обращаться с таким полем как с обычной строкой:
user.city.toUpperCase() // ошибка: 'user.city' is possibly 'undefined'Сначала проверьте, что поле есть, — и внутри проверки это уже точно строка:
if (user.city !== undefined) {
return user.city.toUpperCase() // можно
}Это и есть главная польза строгого режима: «а вдруг тут undefined?»
спрашивает компилятор, а не пользователь, у которого всё сломалось.
readonly: только для чтения
Поле, которое после создания менять нельзя, помечают readonly:
const order: { readonly id: number; status: string } = { id: 7, status: 'новый' }
order.status = 'оплачен' // можно
order.id = 8 // ошибка: Cannot assign to 'id' because it is a read-only propertyУ массивов тоже есть версия только для чтения — readonly number[]. У неё
нет методов, которые меняют массив на месте: push, pop, sort,
splice и других.
function best(scores: readonly number[]): number {
scores.sort() // ошибка: Property 'sort' does not exist on type 'readonly number[]'
return Math.max(...scores) // можно: max ничего не меняет
}Зачем это функции? Подпись readonly number[] — обещание: «я ваш массив не
трону». Помните, как в JavaScript sort незаметно переставлял исходный
массив? Здесь такую ошибку не пропустит компилятор. А ещё в функцию с
readonly-параметром можно передать и обычный массив, и массив только для
чтения — а в функцию с number[] массив только для чтения не пройдёт.
readonly проверяется только компилятором. При запуске он стирается, как и
все типы, — это не Object.freeze.
Кортежи: массив точной длины
Иногда массив — это не список однотипных вещей, а пара или тройка значений на своих местах. Например, точка на карте: широта и долгота. Для этого есть кортеж:
const point: [number, number] = [55.75, 37.62]
point[0] // number
point[2] // ошибка: Tuple type '[number, number]' of length '2' has no element at index '2'Кортеж удобно возвращать из функции, когда результатов несколько. Но тип
результата придётся написать: без него компилятор увидит в [a, b] просто
number[] — массив неизвестной длины.
function minMax(values: number[]): [number, number] {
return [Math.min(...values), Math.max(...values)]
}
const [low, high] = minMax([3, 9, 1]) // low и high — numberДеструктуризация работает так же, как в JavaScript, и каждая переменная получает свой тип.
Что запомнить
number[]иArray<number>— массив чисел.{ name: string; age: number }— объект с такими полями.city?: string— поля может не быть; при чтении этоstring | undefined.readonlyу поля — его нельзя перезаписать;readonly number[]— массив нельзя менять.[number, number]— кортеж: ровно два числа.