Avatar ·

Как правильно объявить массив объектов в TypeScript: полное руководство для новичков

Всем привет! Я только начал изучать TypeScript и столкнулся с проблемой при объявлении массива объектов. Пытаюсь написать простой функционал для списка пользователей, но TypeScript постоянно выдаёт ошибки. Вот мой код: ```typescript const users = [ { name: "Иван", age: 25 }, { name: "Мария", age: 30 }, { name: "Пётр", age: 28 } ]; function getUserNames(users: ???) { return users.map(user => user.name); } ``` Я пробовал разные варианты: `users: Array`, `users: any[]`, но либо TypeScript ругается, либо теряется автодополнение в редакторе. Например, когда я пишу `users: any[]`, то при обращении к `user.name` редактор не подсказывает свойства объекта, и я могу случайно написать несуществующее поле. Ещё одна проблема: иногда я хочу, чтобы в массиве были объекты с разными обязательными и необязательными полями. Например, у части пользователей может быть поле `phone`, а у других — нет. Подскажите, пожалуйста, как правильно объявлять массивы объектов в TypeScript, чтобы сохранить автодополнение и типовую безопасность? Какой синтаксис считается лучшей практикой в 2024 году? Заранее спасибо за помощь! <<>>

Основные способы объявления массива объектов в TypeScript

В TypeScript существует несколько способов объявить массив объектов. Каждый подход имеет свои преимущества и используется в зависимости от ситуации. Рассмотрим все варианты от простых к более сложным.

1. Inline-типизация (для простых случаев)

Самый быстрый способ — объявить тип прямо в параметре или переменной:

const users: { name: string; age: number }[] = [
  { name: "Иван", age: 25 },
  { name: "Мария", age: 30 }
];

function getUserNames(users: { name: string; age: number }[]) {
  return users.map(user => user.name);
}

Плюсы: быстро, не нужно создавать отдельный тип.
Минусы: если массив используется в нескольких местах, придётся дублировать тип.

2. Использование type alias (рекомендуется)

Лучшая практика — вынести тип объекта в отдельный алиас:

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

const users: User[] = [
  { name: "Иван", age: 25 },
  { name: "Мария", age: 30 }
];

function getUserNames(users: User[]) {
  return users.map(user => user.name);
}

Такой подход даёт полное автодополнение в редакторе и возможность переиспользовать тип User в других функциях.

3. Использование interface (альтернатива type)

interface IUser {
  name: string;
  age: number;
}

const users: IUser[] = [
  { name: "Иван", age: 25 },
  { name: "Мария", age: 30 }
];

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

4. Массив с необязательными полями

Если некоторые поля могут отсутствовать, используйте оператор ?:

type User = {
  name: string;
  age: number;
  phone?: string; // необязательное поле
};

const users: User[] = [
  { name: "Иван", age: 25, phone: "+7-999-123-45-67" },
  { name: "Мария", age: 30 } // phone отсутствует — это нормально
];

5. Generic Array<T> синтаксис

Можно использовать дженерики вместо квадратных скобок:

const users: Array<User> = [
  { name: "Иван", age: 25 },
  { name: "Мария", age: 30 }
];

Этот синтаксис полностью эквивалентен User[]. Выбор — дело вкуса.

6. Массив с разными типами объектов (union)

Если в массиве могут быть объекты разных форм, используйте объединение типов:

type Admin = {
  name: string;
  role: "admin";
  permissions: string[];
};

type RegularUser = {
  name: string;
  role: "user";
  age: number;
};

type Person = Admin | RegularUser;

const people: Person[] = [
  { name: "Иван", role: "admin", permissions: ["read", "write"] },
  { name: "Мария", role: "user", age: 30 }
];

7. ReadonlyArray — если массив не должен изменяться

const users: ReadonlyArray<User> = [
  { name: "Иван", age: 25 }
];
// users.push({ name: "Пётр", age: 28 }); // Ошибка! Нельзя изменять

Что выбрать новичку?

  • Для простых скриптов — inline-типизация
  • Для реальных проектов — type alias + User[]
  • Если планируете расширять тип — interface
  • Для необязательных полей — оператор ?
  • Для гетерогенных массивов — union types

Главное правило: всегда явно указывайте тип массива объектов. Это даёт автодополнение, защиту от ошибок и самодокументируемый код.

<<>>

Альтернативный подход: утилитарные типы и Record

Иногда удобнее использовать встроенные утилиты TypeScript для создания массива объектов, особенно когда структура динамическая или повторяющаяся.

1. Record для однотипных объектов

type User = Record<'name' | 'age' | 'phone', string | number>;

const users: User[] = [
  { name: "Иван", age: 25, phone: "+7-999-123-45-67" },
  { name: "Мария", age: 30, phone: "+7-999-987-65-43" }
];

Недостаток: все поля становятся одного типа (string | number), теряется точность.

2. Tuple для фиксированной структуры

Если массив всегда содержит строго определённое количество объектов:

type Pair = [User, User]; // ровно два объекта

const pair: Pair = [
  { name: "Иван", age: 25 },
  { name: "Мария", age: 30 }
];

3. as const для литеральных типов

Если массив объектов не будет изменяться и вы хотите максимально точные типы:

const users = [
  { name: "Иван", age: 25 },
  { name: "Мария", age: 30 }
] as const;
// users[0].name — тип "Иван", а не string

4. Когда использовать эти подходы

  • Record — для быстрой типизации, когда точность не критична
  • Tuple — для пар или троек объектов с разным смыслом
  • as const — для конфигураций, enum-подобных массивов

Однако для 90% случаев лучше всего подходит классический подход с type или interface, описанный в первом ответе. Эти альтернативы — для специфических сценариев.