Массивы объектов в TypeScript: полный гайд с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Массивы объектов в TypeScript: пошаговый туториал для тех, кто устал от ошибок

Вы когда-нибудь писали на TypeScript и вдруг получали красное подчеркивание от IDE, когда пытались добавить объект в массив? Знакомо, правда? Вопрос "How can I define an array of objects?" — один из самых популярных на Stack Overflow. И не зря: без правильной типизации массивов объектов ваш код превращается в источник багов и головной боли.

В этом туториале мы разберем все способы определения массива объектов в TypeScript — от простых до продвинутых. Вы узнаете, как избежать распространенных ошибок, как использовать интерфейсы и как сделать ваш код не только рабочим, но и красивым. Поехали!

Почему просто [] не работает?

Допустим, вы пишете:

let users = [];

TypeScript видит это как never[] — массив, который никогда не должен содержать элементов. Попробуйте добавить туда объект:

users.push({ name: 'Анна', age: 25 }); // Ошибка!

IDE закричит: "Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'never'." TypeScript не знает, что вы собираетесь хранить в массиве, поэтому блокирует любые добавления.

Решение — явно указать тип массива. Давайте разберем все варианты.

Способ 1: Базовая аннотация типа — квадратные скобки

Самый простой и интуитивный способ — использовать синтаксис Тип[]:

let users: { name: string; age: number }[] = [];

users.push({ name: 'Анна', age: 25 }); // ✅ Работает
users.push({ name: 'Иван', age: 30 }); // ✅ Работает

Здесь мы говорим TypeScript: "Эй, это массив объектов, у каждого из которых есть свойства name (строка) и age (число)". IDE теперь довольна, автодополнение работает, ошибки типов отлавливаются на этапе компиляции.

Минус: если структура объекта сложная, описание прямо в аннотации становится громоздким. Для таких случаев придумали интерфейсы.

Способ 2: Интерфейсы — ваш лучший друг

Интерфейсы позволяют вынести описание объекта в отдельную сущность и переиспользовать её. Это делает код чище и понятнее:

interface User {
  id: number;
  name: string;
  age: number;
  email?: string; // необязательное свойство
}

let users: User[] = [];

users.push({ id: 1, name: 'Анна', age: 25 });
users.push({ id: 2, name: 'Иван', age: 30, email: 'ivan@example.com' }); // ✅

Теперь если вы попытаетесь добавить объект без поля id или с неправильным типом, TypeScript выдаст ошибку. Интерфейсы особенно полезны, когда один и тот же тип объекта используется в нескольких местах.

А что насчет type?

Вместо interface можно использовать type:

type User = {
  id: number;
  name: string;
  age: number;
};

let users: User[] = [];

Разница между interface и type — тема для отдельной статьи. Для массивов объектов можно использовать оба варианта, но interface чаще применяется для описания форм объектов.

Способ 3: Generic-синтаксис Array<Type>

Альтернативный способ — использовать дженерики:

let users: Array<{ name: string; age: number }> = [];

// Или с интерфейсом:
let users: Array<User> = [];

Оба синтаксиса (Type[] и Array<Type>) работают одинаково. Выбор — дело вкуса и контекста. Лично я предпочитаю Type[] для простых типов и Array<Type> для сложных дженерик-конструкций. Но главное — быть последовательным в проекте.

Способ 4: Создание массива с начальными объектами

Часто массив нужно не просто объявить, но и сразу наполнить данными. TypeScript сам выведет тип из литерала:

let users = [
  { id: 1, name: 'Анна', age: 25 },
  { id: 2, name: 'Иван', age: 30 }
]; // Тип выведен: { id: number; name: string; age: number }[]

Но будьте осторожны: если все объекты имеют одинаковую структуру, выведение типа работает идеально. Если структуры различаются, TypeScript может вывести объединение типов (union type), что не всегда желательно.

let mixed = [
  { id: 1, name: 'Анна' },
  { id: 2, name: 'Иван', age: 30 }
]; // Тип: { id: number; name: string } | { id: number; name: string; age: number }[]

Лучше явно указать тип массива, если структура объектов неоднородна.

Способ 5: Вложенные массивы и сложные объекты

Объекты могут содержать другие объекты или массивы. Интерфейсы справляются и с этим:

interface Post {
  id: number;
  title: string;
  author: User; // другой интерфейс
  tags: string[]; // массив строк
  comments: Comment[]; // массив объектов Comment
}

interface Comment {
  id: number;
  text: string;
  userId: number;
}

let posts: Post[] = [
  {
    id: 1,
    title: 'TypeScript для начинающих',
    author: { id: 1, name: 'Анна', age: 25 },
    tags: ['typescript', 'tutorial'],
    comments: [
      { id: 1, text: 'Отличная статья!', userId: 2 }
    ]
  }
];

TypeScript проверит каждый уровень вложенности. Если в комментарии не будет поля text или оно будет числом — компилятор выдаст ошибку. Это экономит часы отладки.

Типизация методов массива

Когда массив правильно типизирован, все методы массива (map, filter, reduce, find) работают с автодополнением:

let users: User[] = [
  { id: 1, name: 'Анна', age: 25 },
  { id: 2, name: 'Иван', age: 30 },
  { id: 3, name: 'Мария', age: 28 }
];

// filter — возвращает отфильтрованный массив того же типа
const adults = users.filter(user => user.age >= 18); // User[]

// map — можно изменить тип
const names = users.map(user => user.name); // string[]

// find — возвращает User | undefined
const user = users.find(u => u.id === 2); // User | undefined

// reduce — тип аккумулятора задается явно
const totalAge = users.reduce((sum, user) => sum + user.age, 0);                

Блоги

Рекомендаций по книгам