- Главная
- Typescript
- Структуры данных в Typescript — Arrays и Objects
Структуры данных в Typescript — Arrays и Objects
Содержание
- Типизация массивов
- Массивы с несколькими типами
- Чтение элементов из массива
- Типизация объектов
- Использование объектов в функциях
- Вложенные объекты
- Опциональные свойства в объектах
- Псевдонимы типов для переиспользования структур
- Опциональные элементы в массивах
- Проверка на наличие свойств
- Практическое упражнение
- Итог
В прошлом уроке мы научились аннотировать простые переменные и функции с примитивными типами. Но в реальной разработке мы редко работаем с изолированными строками или числами. Обычно данные приходят в виде списков (массивов) и структур (объектов). TypeScript даёт нам мощные инструменты для описания формы этих данных.
Сегодня мы разберём, как типизировать массивы и объекты, как работать с опциональными свойствами и как создавать переиспользуемые описания структур.
Типизация массивов
Массивы в TypeScript можно типизировать двумя способами. Выберите тот, который вам удобнее — они эквивалентны.
Способ 1: Синтаксис с квадратными скобками (рекомендуемый)
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
let booleans: boolean[] = [true, false, true];
Способ 2: Generic-синтаксис с Array<T>
let numbers: Array<number> = [1, 2, 3, 4, 5];
let names: Array<string> = ["Alice", "Bob", "Charlie"];
Оба способа работают одинаково. Лично я предпочитаю первый — он короче и интуитивно понятнее.
Самое важное: TypeScript будет следить, чтобы в массиве были элементы только указанного типа:
let scores: number[] = [10, 20, 30];
scores.push(40); // ✅
scores.push("50"); // ❌ Ошибка: Argument of type 'string' is not assignable to parameter of type 'number'
Массивы с несколькими типами
Иногда массив может содержать элементы разных типов. Здесь нам снова помогают Union Types:
let mixed: (string | number)[] = ["Alice", 25, "Bob", 30];
// Или альтернативный синтаксис:
let mixed2: Array<string | number> = ["Alice", 25, "Bob", 30];
Это позволяет хранить разнородные данные, но при этом сохранять контроль.
Чтение элементов из массива
Когда вы обращаетесь к элементу массива по индексу, TypeScript выводит его тип:
let fruits: string[] = ["apple", "banana", "orange"];
let firstFruit: string = fruits[0]; // ✅ string
let invalidFruit = fruits[10]; // TypeScript выводит string, но в рантайме будет undefined
Здесь есть важный нюанс: TypeScript не знает длину массива на этапе компиляции, поэтому он предполагает, что элемент существует, и возвращает тип string. Но если вы включите strict режим (а мы его включили), TypeScript предложит вам более безопасный подход:
// В строгом режиме TypeScript подскажет, что элемент может быть undefined
let maybeFruit: string | undefined = fruits[10]; // Более безопасно
Типизация объектов
Объекты — основа JavaScript. TypeScript позволяет описать их структуру с помощью объектных аннотаций — фигурных скобок, внутри которых перечисляются свойства и их типы.
// Описываем объект с конкретной структурой
let user: { name: string; age: number; email: string } = {
name: "Alice",
age: 30,
email: "alice@example.com"
};
// TypeScript следит за структурой
user.name = "Bob"; // ✅
user.age = "thirty"; // ❌ Ошибка: Type 'string' is not assignable to type 'number'
user.phone = "123-456"; // ❌ Ошибка: Property 'phone' does not exist on type '{ name: string; age: number; email: string; }'
Объектные аннотации отлично работают для простых случаев, но когда структура становится сложной или используется в нескольких местах, лучше использовать интерфейсы или type aliases — мы поговорим о них в следующих уроках.
Оживи свой сайт. Освой JavaScript!
Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.
От теории — к реальным скриптам в твоём портфолио.
Использование объектов в функциях
Объектные аннотации особенно полезны в параметрах функций:
function printUser(user: { name: string; age: number }): void {
console.log(`${user.name} is ${user.age} years old`);
}
printUser({ name: "Alice", age: 30 }); // ✅
printUser({ name: "Bob", age: "25" }); // ❌ Ошибка: age должен быть number
Вложенные объекты
Объекты могут содержать другие объекты — и TypeScript прекрасно это обрабатывает:
let company: {
name: string;
address: {
street: string;
city: string;
zipCode: number;
};
employees: string[];
} = {
name: "Tech Corp",
address: {
street: "123 Main St",
city: "New York",
zipCode: 10001
},
employees: ["Alice", "Bob", "Charlie"]
};
Опциональные свойства в объектах
В реальных проектах не все свойства объекта обязательны. Например, у пользователя может не быть номера телефона. TypeScript позволяет сделать свойства опциональными с помощью вопросительного знака ?.
// phone и age — опциональны
let user: {
name: string;
age?: number;
phone?: string;
} = {
name: "Alice",
age: 30 // phone отсутствует — это нормально
};
// Можно создать и без age
let anotherUser: { name: string; age?: number } = {
name: "Bob"
};
Когда свойство опционально, TypeScript автоматически добавляет к его типу undefined. Это означает, что при работе с таким свойством нужно быть осторожным:
function getAge(user: { name: string; age?: number }): string {
if (user.age !== undefined) {
return `Age: ${user.age}`;
}
return "Age not provided";
}
console.log(getAge({ name: "Alice", age: 30 })); // "Age: 30"
console.log(getAge({ name: "Bob" })); // "Age not provided"
Псевдонимы типов для переиспользования структур
Вспомним из прошлого урока: Type Aliases позволяют дать имя любому типу. Это особенно полезно для объектов, которые используются в нескольких местах.
// Создаём псевдоним для структуры пользователя
type User = {
name: string;
age: number;
email?: string; // Опциональное свойство
};
// Используем в разных местах
function registerUser(user: User): void {
console.log(`Registering ${user.name}`);
}
function updateUser(user: User, newName: string): User {
return { ...user, name: newName };
}
// Создаём массив пользователей
let users: User[] = [
{ name: "Alice", age: 30, email: "alice@example.com" },
{ name: "Bob", age: 25 } // email необязателен
];
Это делает код чище и уменьшает дублирование. Если структура пользователя изменится, вы обновите её в одном месте — в type User.
Опциональные элементы в массивах
Мы уже знаем, что можно сделать опциональными свойства объектов. А что насчёт массивов? У массивов есть специальный синтаксис для кортежей (tuples) — массивов фиксированной длины с элементами определённых типов.
// Кортеж: ровно два элемента, первый — строка, второй — число
let person: [string, number] = ["Alice", 30];
// Можно сделать элементы опциональными
let optionalTuple: [string, number?] = ["Alice"]; // Второй элемент необязателен
optionalTuple = ["Bob", 25]; // Или с двумя элементами
// Кортеж с rest-элементом (несколько элементов одного типа в конце)
let tupleWithRest: [string, ...number[]] = ["Alice", 30, 40, 50];
Кортежи полезны, когда вы точно знаете структуру массива, например, для координат [x, y] или для возврата нескольких значений из функции.
Проверка на наличие свойств
При работе с опциональными свойствами и объединениями типов важно проверять существование свойств перед их использованием. TypeScript поддерживает оператор in для проверки наличия свойства в объекте.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird): void {
if ("swim" in animal) {
animal.swim(); // TypeScript знает: это Fish
} else {
animal.fly(); // А это Bird
}
}
Практическое упражнение
Теперь ваша очередь применить знания. Вот задача на типизацию массивов и объектов.
Задание: Напишите TypeScript-код для следующего сценария:
Создайте тип Product с полями:
id(число)name(строка)price(число)inStock(булево значение, опционально)
Создайте массив products с несколькими товарами.
- Напишите функцию
findProduct, которая принимает массив продуктов и ID (число или строку) и возвращает продукт илиundefined. - Напишите функцию
getTotalValue, которая принимает массив продуктов и возвращает общую стоимость всех товаров (сумму цен). - Создайте кортеж
apiResponseдля описания ответа от сервера: первый элемент — статус (число), второй — данные (массив продуктов).
Решение (не подглядывайте сразу!):
// 1. Тип Product
type Product = {
id: number;
name: string;
price: number;
inStock?: boolean; // опционально
};
// 2. Массив продуктов
const products: Product[] = [
{ id: 1, name: "Laptop", price: 999.99, inStock: true },
{ id: 2, name: "Mouse", price: 49.99, inStock: true },
{ id: 3, name: "Keyboard", price: 89.99 } // inStock не указан
];
// 3. Функция поиска
function findProduct(products: Product[], id: number | string): Product | undefined {
const numericId = typeof id === "string" ? parseInt(id, 10) : id;
return products.find(product => product.id === numericId);
}
// 4. Функция подсчёта общей стоимости
function getTotalValue(products: Product[]): number {
return products.reduce((total, product) => total + product.price, 0);
}
// 5. Кортеж для ответа API
const apiResponse: [number, Product[]] = [200, products];
// Проверка работы
console.log(findProduct(products, 2)); // { id: 2, name: "Mouse", price: 49.99, inStock: true }
console.log(findProduct(products, "3")); // { id: 3, name: "Keyboard", price: 89.99 }
console.log(getTotalValue(products)); // 1139.97
console.log(apiResponse); // [200, products]
Итог
Сегодня мы освоили ключевые структуры данных в TypeScript:
- Массивы —
type[]илиArray<type>для однородных массивов, и(type1 | type2)[]для смешанных. - Объекты — описание структуры с помощью фигурных скобок и типов свойств.
- Опциональные свойства — с помощью
?для свойств, которых может не быть. - Псевдонимы типов — создание переиспользуемых имен для типов объектов.
- Кортежи — массивы фиксированной длины с заданными типами элементов.
Теперь вы умеете описывать практически любые данные, с которыми работаете в реальных проектах. В следующем уроке мы расширим наши возможности и детально разберём Union Types — механизм, который позволяет объединять типы и делать код более гибким, и познакомимся с псевдонимами типов на более глубоком уровне.
Помните: чем точнее вы описываете структуру данных, тем больше ошибок TypeScript сможет отловить до того, как ваш код попадёт в продакшен.