Avatar ·

Как создать пустой типизированный массив в TypeScript?

Всем привет! Я только начал изучать TypeScript после JavaScript и столкнулся с проблемой при создании пустых массивов. В JavaScript я часто делал так: ```javascript let items = []; ``` И потом просто добавлял элементы через push. Но в TypeScript я получаю ошибку, когда пытаюсь сделать то же самое: ```typescript let items = []; items.push('hello'); // Error: Argument of type 'string' is not assignable to parameter of type 'never' ``` Я понимаю, что TypeScript выводит тип `never[]`, потому что массив пустой, и компилятор не знает, какие элементы там будут. Но как правильно объявить пустой массив, который потом будет содержать, например, строки или объекты определённого типа? Я пробовал несколько вариантов: ```typescript let items: string[] = []; // Работает, но для строк let items: Array = []; // Тоже работает ``` Но что если у меня сложный тип? Например, массив объектов с интерфейсом User? Или массив, который будет содержать разные типы (юнион)? Как правильно описать это в TypeScript, чтобы не было ошибок и код был типобезопасным? Буду благодарен за примеры с реальными кейсами — допустим, массив задач (Todo), куда я добавляю элементы позже в коде. Заранее спасибо! <<>>

Основной способ: аннотация типа при объявлении

Самый распространённый и правильный способ создать пустой типизированный массив в TypeScript — это явно указать тип массива при объявлении переменной. TypeScript выведет тип never[] из пустого литерала [], поэтому без аннотации вы не сможете ничего в него добавить.

Вот как это выглядит для разных сценариев:

// Простой массив строк
let names: string[] = [];
names.push('Alice'); // ✅ OK

// Альтернативный синтаксис с generic
let names2: Array<string> = [];
names2.push('Bob'); // ✅ OK

// Массив чисел
let scores: number[] = [];
scores.push(100);

// Массив с юнион-типом (строка или число)
let mixed: (string | number)[] = [];
mixed.push('hello');
mixed.push(42);

Работа с интерфейсами и объектами

Для сложных типов, таких как объекты, используйте интерфейс или type alias:

interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

// Пустой массив задач
let todos: Todo[] = [];

// Теперь можно добавлять объекты, соответствующие интерфейсу
todos.push({
  id: 1,
  title: 'Изучить TypeScript',
  completed: false
});

// Если попытаться добавить неполный объект — TypeScript выдаст ошибку
todos.push({
  id: 2,
  title: 'Купить продукты'
  // ❌ Ошибка: missing 'completed'
});

Вариант с явным указанием типа через as

Иногда (например, при работе с библиотеками или сложной инициализацией) можно использовать приведение типа:

let items = [] as string[];
items.push('test'); // ✅ OK

// Для интерфейсов
let todos2 = [] as Todo[];
todos2.push({ id: 1, title: 'Задача', completed: true });

Этот синтаксис короче, но будьте осторожны: as не проверяет, что массив действительно пуст — он просто "говорит" TypeScript'у, что массив будет такого типа.

Использование конструктора Array

Ещё один вариант — через generic конструктор:

let numbers = new Array<number>();
numbers.push(1, 2, 3);

let todos3 = new Array<Todo>();
todos3.push({ id: 1, title: 'Дело', completed: false });

Однако этот способ менее популярен, так как он создаёт объект через конструктор, что чуть менее производительно и менее читаемо для простых случаев.

Что выбрать?

  • Для простых типов: let arr: string[] = []; — самый читаемый вариант.
  • Для интерфейсов и сложных типов: let arr: Todo[] = []; — явно и типобезопасно.
  • Если нужно сократить код: let arr = [] as Todo[]; — компактно, но может скрыть ошибки при невнимательности.
  • Для динамического создания: new Array<Type>() — редко, но допустимо.

Главное правило: всегда указывайте тип пустого массива явно, иначе TypeScript выведет never[], и вы не сможете ничего в него добавить без ошибки компиляции.

<<>>

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

Иногда нужно не просто создать пустой массив, а гарантировать, что он останется неизменным (например, для конфигураций или констант). В таких случаях полезен модификатор readonly:

// Неизменяемый пустой массив строк
const emptyReadonly: readonly string[] = [];
// emptyReadonly.push('test'); ❌ Ошибка: push не существует на readonly массиве

Также для пустых массивов с фиксированной длиной (кортежей) можно использовать:

// Пустой кортеж из двух элементов
let pair: [string, number] = ['hello', 42]; // не пустой, но можно так:
let emptyPair: [string, number] = [] as any; // ❌ Плохая практика

// Лучше для кортежей:
type OptionalPair = [string?, number?];
let maybePair: OptionalPair = []; // ✅ Пустой кортеж

Когда это полезно? Если вы пишете библиотеку или API, где массив должен быть пустым и не должен изменяться — readonly даёт дополнительную защиту на уровне типов.

Ещё один нюанс: при работе с дженериками (функции, возвращающие пустой массив) используйте:

function createEmptyArray<T>(): T[] {
  return [];
}

const nums = createEmptyArray<number>(); // nums: number[]
nums.push(5);

Этот паттерн часто встречается в фабричных методах и помогает сохранить типобезопасность при создании пустых контейнеров.