Хотите заказать веб-сайт? Связаться с нами

Структуры данных в Typescript — Arrays и Objects

В прошлом уроке мы научились аннотировать простые переменные и функции с примитивными типами. Но в реальной разработке мы редко работаем с изолированными строками или числами. Обычно данные приходят в виде списков (массивов) и структур (объектов). TypeScript даёт нам мощные инструменты для описания формы этих данных.

Сегодня мы разберём, как типизировать массивы и объекты, как работать с опциональными свойствами и как создавать переиспользуемые описания структур.

Структуры данных в Typescript — Arrays и Objects

Типизация массивов

Массивы в 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 с полями:

  1. id (число)
  2. name (строка)
  3. price (число)
  4. inStock (булево значение, опционально)

Создайте массив products с несколькими товарами.

  1. Напишите функцию findProduct, которая принимает массив продуктов и ID (число или строку) и возвращает продукт или undefined.
  2. Напишите функцию getTotalValue, которая принимает массив продуктов и возвращает общую стоимость всех товаров (сумму цен).
  3. Создайте кортеж 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:

  1. Массивыtype[] или Array<type> для однородных массивов, и (type1 | type2)[] для смешанных.
  2. Объекты — описание структуры с помощью фигурных скобок и типов свойств.
  3. Опциональные свойства — с помощью ? для свойств, которых может не быть.
  4. Псевдонимы типов — создание переиспользуемых имен для типов объектов.
  5. Кортежи — массивы фиксированной длины с заданными типами элементов.

Теперь вы умеете описывать практически любые данные, с которыми работаете в реальных проектах. В следующем уроке мы расширим наши возможности и детально разберём Union Types — механизм, который позволяет объединять типы и делать код более гибким, и познакомимся с псевдонимами типов на более глубоком уровне.

Помните: чем точнее вы описываете структуру данных, тем больше ошибок TypeScript сможет отловить до того, как ваш код попадёт в продакшен.

Теги: