Avatar ·

Как правильно объявить массив объектов в TypeScript?

Привет! Я только начал изучать TypeScript и столкнулся с проблемой. Пишу небольшое приложение для управления задачами. У меня есть массив объектов, каждый объект описывает задачу: у неё есть название, статус выполнения и дата создания. В JavaScript я бы просто написал: const tasks = [ { title: 'Купить молоко', done: false, createdAt: new Date() }, { title: 'Сделать зарядку', done: true, createdAt: new Date() } ]; Но в TypeScript мне нужно указать тип. Я пробовал разные варианты, но не уверен, какой из них правильный и когда какой использовать. Пробовал так: const tasks: Array = [...]; const tasks: object[] = [...]; const tasks: any[] = [...]; Первый вариант работает, но я не могу обратиться к полю title — TypeScript ругается, что свойства не существует на типе object. Второй — то же самое. Третий — работает, но теряется вся смысловая нагрузка, и редактор кода не подсказывает поля объектов. Ещё я слышал про интерфейсы и типы, но не понимаю, как их применить к массиву. Подскажите, пожалуйста, как правильно объявить типизированный массив объектов, чтобы TypeScript проверял структуру каждого элемента и подсказывал поля при написании кода? Какие есть лучшие практики для этого в реальных проектах? <<>>

Правильный способ: используйте интерфейс или type для описания объекта

Ключевая идея TypeScript — описывать форму данных, а не просто указывать "это объект". Для массива объектов нужно сначала определить тип самого объекта, а затем указать, что массив состоит из таких объектов.

Шаг 1: Определите тип объекта

// Через интерфейс (рекомендуется для объектов, которые могут расширяться)
interface Task {
  title: string;
  done: boolean;
  createdAt: Date;
}

// Или через type (если нужен union/intersection, или просто привыкли)
type Task = {
  title: string;
  done: boolean;
  createdAt: Date;
};

Шаг 2: Объявите массив этого типа

// Вариант 1 — синтаксис с квадратными скобками (наиболее популярный)
const tasks: Task[] = [
  { title: 'Купить молоко', done: false, createdAt: new Date() },
  { title: 'Сделать зарядку', done: true, createdAt: new Date() }
];

// Вариант 2 — через generic Array<T> (менее распространён, но тоже корректен)
const tasks: Array<Task> = [
  { title: 'Купить молоко', done: false, createdAt: new Date() },
  // ...
];

Почему это работает?

  • TypeScript проверяет каждое свойство — если вы попытаетесь добавить объект без поля title, компилятор выдаст ошибку.
  • Автодополнение в IDE — при наборе tasks[0]. редактор предложит title, done, createdAt.
  • Безопасность типов — вы не сможете случайно присвоить массиву строку или число.

Дополнительные возможности

// Необязательные поля (?)
interface Task {
  title: string;
  done: boolean;
  createdAt: Date;
  completedAt?: Date; // может отсутствовать
}

// Только для чтения (readonly)
interface Task {
  readonly id: number;
  title: string;
  done: boolean;
}

// Массив только для чтения
const tasks: ReadonlyArray<Task> = [...];
// или const tasks: readonly Task[] = [...];

Практический пример с функциями

interface Task {
  id: number;
  title: string;
  done: boolean;
}

// Функция принимает массив задач
function getPendingTasks(tasks: Task[]): Task[] {
  return tasks.filter(task => !task.done);
}

// Функция возвращает массив задач
function createSampleTasks(): Task[] {
  return [
    { id: 1, title: 'Купить молоко', done: false },
    { id: 2, title: 'Сделать зарядку', done: true }
  ];
}

// Использование
const allTasks = createSampleTasks();
const pendingTasks = getPendingTasks(allTasks);
console.log(pendingTasks[0].title); // ✅ TypeScript знает, что это string

Что НЕ стоит делать

  • const tasks: object[] = [...] — теряете информацию о структуре, нельзя обращаться к полям.
  • const tasks: any[] = [...] — отключаете всю проверку типов, смысл TypeScript теряется.
  • const tasks = [...] as any — то же самое, только хуже (подавляет проверку даже на уровне присваивания).

Итог

Всегда определяйте интерфейс или тип для объекта, а затем используйте ИмяТипа[] или Array<ИмяТипа>. Это даёт максимальную безопасность и удобство разработки.

<<>>

Альтернативный подход: встраивание типа прямо в объявление массива

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

const tasks: { title: string; done: boolean; createdAt: Date }[] = [
  { title: 'Купить молоко', done: false, createdAt: new Date() },
  { title: 'Сделать зарядку', done: true, createdAt: new Date() }
];

Это работает, но имеет недостатки:

  • Дублирование кода — если такой же массив понадобится в другом месте, придётся повторять тип.
  • Сложность чтения — длинная строка с типом внутри квадратных скобок ухудшает читаемость.
  • Ошибки при изменении — если нужно добавить поле, придётся менять тип в каждом месте использования.

Когда это может быть оправдано?

  • Временные данные в небольшом скрипте или прототипе.
  • Массив с уникальной структурой, которая точно не повторится (например, результат парсинга конкретного API).
  • Быстрое тестирование идеи без создания лишних файлов.

Совет

Даже если вы пишете inline-тип, старайтесь использовать type или interface для сложных объектов. Это делает код самодокументируемым и упрощает рефакторинг. В больших проектах inline-типы обычно считаются плохой практикой.