На предыдущих уроках мы с вами разобрались с базовыми типами, функциями и интерфейсами. Мы научились описывать отдельные сущности. Но в реальном мире данные редко существуют поодиночке. Чаще всего мы работаем с коллекциями данных, списком пользователей, каталогом товаров, историей сообщений и так далее.
Сегодня, на пятом уроке, мы будем учиться работать именно с коллекциями. Мы изучим в два мощнейших инструмента в TypeScript: массивы (Arrays) и кортежи (Tuples). Мы не просто узнаем их синтаксис, но и поймем, в каких ситуациях что следует использовать и как система типов TypeScript помогает нам избежать целый класс ошибок уже на этапе написания кода. Это один из тех уроков, после которого вы почувствуете, насколько TypeScript делает ваш код надежнее и предсказуемее.
Типизация массивов: type[] и Array<type>
В обычном JavaScript массив может содержать элементы любых типов. Вы можете создать массив, где первым элементом будет число, вторым строка, третьим объект и так далее. Гибко? Безусловно. Но именно это является частым источником ошибок. Вы ожидаете, что в массиве только числа, пытаетесь вызвать для каждого элемента метод toFixed(), а в какой-то момент там оказывается строка и ваше приложение падает.
TypeScript решает эту проблему elegantly, вводя строгую типизацию для массивов. Это означает, что мы явно указываем компилятору (и самим себе!): «В этом массиве могут находиться элементы только такого-то типа». Сделать это можно двумя абсолютно идентичными способами.
Первый способ, более распространенный и лаконичный, это использование синтаксиса с квадратными скобками после типа. Выглядит это так: type[].
// Массив чисел let numbers: number[] = [1, 2, 3, 4, 5]; // Массив строк let strings: string[] = ['a', 'b', 'c']; // Массив булевых значений let booleans: boolean[] = [true, false, true]; // Массив объектов определенного типа (используем интерфейс с прошлого урока!) interface User { name: string; age: number; } let users: User[] = [ { name: 'Максим', age: 30 }, { name: 'Анна', age: 25 } ];
Попробуйте добавить в массив numbers строку и TypeScript немедленно укажет на ошибку:
numbers.push('hello'); // Ошибка: Argument of type 'string' is not assignable to parameter of type 'number'.
Второй способ, использование дженерик-синтаксиса (обобщений): Array<type>. Мы подробнее поговорим о дженериках позже в курсе, но на базовом уровне вы можете воспринимать это как «массив чего-то». Вместо слова «чего-то» мы подставляем нужный тип.
// Те же самые массивы, но с другим синтаксисом let numbers: Array<number> = [1, 2, 3, 4, 5]; let strings: Array<string> = ['a', 'b', 'c']; let users: Array<User> = [ ... ];
Оба способа абсолютно равнозначны. Выбор между number[] и Array<number> дело вкуса и соглашения по код-стайлу в вашей команде. Лично я, Максим, чаще использую синтаксис type[] для простых типов и Array<type> для сложных, но это лишь моя привычка. TypeScript обработает их одинаково.
Главное преимущество типизированных массивов это безопасность операций. Если массив объявлен как number[], вы можете быть на 100% уверены, что методы map, forEach, filter и другие будут работать именно с числами. Среда разработки (IDE) будет предоставлять корректный автодопpletion, подсказывая методы и свойства, доступные для данного типа. Это невероятно повышает продуктивность и снижает количество опечаток.
Концепция кортежа — массива фиксированной длины с известными типами элементов
А теперь представьте себе другую ситуацию. Вам нужно работать не со списком однотипных данных, а с структурой, где каждое место (индекс) в массиве имеет строго определенное значение и тип. Классический пример работа с координатами. Координата в двумерном пространстве это всегда ровно два элемента: x (число) и y (число). Или, например, данные о человеке в определенном контексте: [имя (строка), возраст (число)].
Для описание таких структур в TypeScript существуют кортежи (Tuples).
Кортеж это особый вид массива, длина которого фиксирована и каждому элементу наперед задан конкретный тип. Объявляется кортеж путем указания типов для каждого элемента в квадратных скобках.
// Кортеж для представления координаты точки в 2D-пространстве let point: [number, number] = [10, 20]; // Кортеж для представления данных пользователя: [имя, возраст, активен?] let userInfo: [string, number, boolean] = ['Максим', 30, true]; // Кортеж для представления кода HTTP-ответа и сообщения let httpStatus: [number, string] = [200, 'OK'];
Если вы попытаетесь нарушить контракт, определенный кортежем, компилятор вас остановит.
point = [5, 10, 15]; // Ошибка: Source has 3 element(s) but target allows only 2. userInfo = [true, 'Максим', 30]; // Ошибка: Type 'boolean' is not assignable to type 'string'. httpStatus = ['OK', 200]; // Ошибка: Type 'string' is not assignable to type 'number'.
Кортежи невероятно полезны, когда функция должна вернуть несколько разнотипных значений. В JavaScript мы часто возвращали из функции массив, а потом деструктурировали его: const [data, error] = fetchSomething();. TypeScript позволяет нам точно описать, что возвращает такая функция.
function getUserData(id: number): [string, number, boolean] { // ... какая-то логика return ['Максим', 30, true]; } const [userName, userAge, isActive] = getUserData(123); // Теперь TypeScript знает, что userName - string, userAge - number, а isActive - boolean.
Важное замечание по работе с кортежами. Хотя их длина фиксирована на этапе объявления, с помощью методов массива like push можно добавить новые элементы. Это поведение является наследием обычных JavaScript-массивов и TypeScript в данном случае не ограничивает вас. Однако это считается антипаттерном, так как нарушает изначальный замысел кортежа. Старайтесь избегать модификации кортежей после их создания.
TypeScript также поддерживает опциональные элементы в кортежах (с помощью символа ?) и rest-элементы в конце кортежа.
// Кортеж, где третий элемент (почта) не обязателен let optionalUser: [string, number, string?] = ['Анна', 25]; optionalUser = ['Анна', 25, 'anna@mail.com']; // тоже ок // Кортеж, который начинается с двух определенных элементов, а потом может быть любое количество строк type StringNumberAndStrings = [number, string, ...string[]]; let sns: StringNumberAndStrings = [1, 'hello', 'world', '!'];
Эти возможности делают кортежи еще более гибким инструментом для описания сложных, но все же структурированных данных.
Практические примеры и задачи
Давайте закрепим знания на реальных примерах и небольших задачах. Не просто копируйте код, а пробуйте печатать его сами, это лучше всего помогает запомнить синтаксис.
Пример 1: Работа с координатами
Давайте создадим простую функцию для работы с 2D-координатами.
// Определяем тип для координаты type Coordinate = [number, number]; // Функция для сложения двух координат function addCoordinates(c1: Coordinate, c2: Coordinate): Coordinate { return [c1[0] + c2[0], c1[1] + c2[1]]; } const coord1: Coordinate = [10, 20]; const coord2: Coordinate = [5, 15]; const result = addCoordinates(coord1, coord2); console.log(result); // [15, 35] // Отлично! А теперь давайте улучшим наш код с помощью деструктуризации для лучшей читаемости: function addCoordinatesImproved([x1, y1]: Coordinate, [x2, y2]: Coordinate): Coordinate { return [x1 + x2, y1 + y2]; }
Задача 1: Типизация списков
Объявите переменные, соблюдая следующие условия:
-
fibonacciSequence— массив чисел, содержащий первые пять чисел Фибоначчи. -
programmingLanguages— массив строк, содержащий названия языков программирования. -
matrix— массив массивов чисел (то естьnumber[][]), представляющий собой матрицу 2×2.
Решение:
let fibonacciSequence: number[] = [0, 1, 1, 2, 3]; let programmingLanguages: string[] = ['JavaScript', 'TypeScript', 'Python', 'Java']; let matrix: number[][] = [[1, 2], [3, 4]]; // или let matrix: Array<Array<number>> = [[1, 2], [3, 4]];
Задача 2: Простая валидация
Напишите функцию validateUserInput(input: string): [boolean, string]. Функция должна проверять, что строка input не пуста и ее длина не превышает 10 символов. Функция должна возвращать кортеж, где:
-
Первый элемент булево значение:
true, если валидация прошла,falseесли нет. -
Второй элемент строка с сообщением об ошибке. Если ошибок нет, верните пустую строку.
Решение:
function validateUserInput(input: string): [boolean, string] { if (input.length === 0) { return [false, 'Строка не может быть пустой']; } if (input.length > 10) { return [false, 'Длина строки не может превышать 10 символов']; } return [true, '']; } // Проверяем работу функции console.log(validateUserInput('')); // [false, 'Строка не может быть пустой'] console.log(validateUserInput('Очень длинная строка')); // [false, 'Длина строки не может превышать 10 символов'] console.log(validateUserInput('Hello')); // [true, '']
Задача 3 (повышенной сложности): Обновление состояния
Это очень распространенный кейс в реактивном программировании. Представьте, что у вас есть кортеж, описывающий состояние загрузки данных: [данные, идет ли загрузка, произошла ли ошибка].
-
Создайте тип
LoadingStateкак кортеж[data: string, isLoading: boolean, error?: string]. Обратите внимание, чтоerrorопциональный элемент. -
Напишите функцию
setLoadingState, которая принимает кортеж этого типа и выводит его в консоль.
Решение:
// 1. Определяем тип. Используем опциональный элемент. type LoadingState = [string, boolean, string?]; // 2. Пишем функцию function setLoadingState(state: LoadingState): void { const [data, isLoading, error] = state; console.log(`Данные: ${data}`); console.log(`Загружается?: ${isLoading}`); if (error) { // проверяем, есть ли ошибка console.log(`Ошибка: ${error}`); } } // Примеры использования: const state1: LoadingState = ['', true]; // Начало загрузки, ошибки еще нет const state2: LoadingState = ['{ "user": "Maxim" }', false]; // Загрузка завершена успешно const state3: LoadingState = ['', false, 'Network Error']; // Загрузка завершена с ошибкой setLoadingState(state1); // Данные: // Загружается?: true setLoadingState(state2); // Данные: { "user": "Maxim" } // Загружается?: false setLoadingState(state3); // Данные: // Загружается?: false // Ошибка: Network Error
Резюме урока
Сегодня мы с вами научились работать с коллекциями данных в TypeScript. Мы начали с массивов и узнали два абсолютно равнозначных способа их типизации: через синтаксис type[] и дженерик-синтаксис Array<type>. Это основа, которая позволяет нам создавать строго типизированные коллекции однородных данных, делая наш код безопасным и удобным для автодополнения.
Затем мы познакомились с более специфической, но не менее важной структурой, это кортежами (Tuples). Мы узнали, что кортеж это массив фиксированной длины, где каждый элемент имеет свой заранее определенный тип. Это мощный инструмент для описания структур данных, где позиция элемента имеет смысловое значение, например, координаты, статусы ответов или возвращаемые значения из функций.
Мы также разобрали опциональные и rest-элементы в кортежах, которые добавляют им гибкости и закрепили все это на практических примерах и задачах.
TypeScript предоставляет нам инструменты для точного описания наших данных. Если у вас список однотипных элементов, то используйте массивы number[]. Если у вас структура из нескольких разнотипных, но логически связанных значений, где важна позиция, то используйте кортежи [number, string]. Это сделает ваш код самодокументируемым и защитит от целого класса ошибок.
В следующих уроке мы поговорим о перечислениях (Enums) и типах-объединениях (Union Types), которые откроют нам новые горизонты для написания выразительного и надежного кода.
Полный курс с уроками по TypeScript для начинающих: https://max-gabov.ru/typescript-dlya-nachinaushih
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


