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

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

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

Начать курс

Введение: почему один тип — это скучно?

Представьте: вы пишете на TypeScript и вдруг сталкиваетесь с ситуацией, когда массив должен хранить данные разных типов. Например, список, где каждый элемент — это либо string, либо number. Или кортеж из имени (строка) и возраста (число).

Многие новички (и не только) задаются вопросом: «Как объявить массив с несколькими типами в TypeScript?» Этот вопрос набрал тысячи просмотров на Stack Overflow — и неспроста. TypeScript строг к типам, но при этом даёт гибкие инструменты для таких случаев.

В этой статье мы разберём все способы работы с массивами смешанных типов: от простых union-типов до кортежей и дженериков. Вы узнаете, когда какой подход использовать, и как не попасть в ловушку any.

1. Union Types: самый простой способ

Самый очевидный вариант — использовать union type (объединение типов). Это когда вы говорите: «Массив может содержать элементы типа string ИЛИ number».

// Массив, который может содержать строки или числа
const mixedArray: (string | number)[] = ['hello', 42, 'world', 100];

// Альтернативная запись (эквивалентна)
const mixedArray2: Array<string | number> = ['hello', 42, 'world', 100];

Здесь (string | number)[] означает: «массив, каждый элемент которого — строка или число». TypeScript будет проверять, что вы не попытаетесь добавить, скажем, булево значение или объект.

Когда это удобно?

  • Когда порядок элементов не важен, а важен только набор допустимых типов.
  • Когда вы работаете с API, которое возвращает массив разнородных данных.
  • Когда вы храните список ID (числа) и имён (строки) в одном массиве.

Подводный камень

При использовании union-типов вы не можете гарантировать, что на первой позиции будет строка, а на второй — число. Типизация говорит только: «в массиве есть строки и числа, но в каком порядке — неизвестно».

const arr: (string | number)[] = [1, 'hello'];
// TypeScript не знает: arr[0] — это number или string?
const first = arr[0]; // тип: string | number
// Чтобы использовать как число, нужно сужать тип:
if (typeof first === 'number') {
    console.log(first.toFixed(2));
}

2. Кортежи (Tuples): когда важен порядок

Если вам нужно, чтобы массив имел фиксированную структуру — например, первым идёт строка, вторым число, третьим булево значение — используйте кортежи (tuple types).

// Кортеж: [строка, число, булево]
const person: [string, number, boolean] = ['Alice', 30, true];

// Кортеж с метками (labels) — более читаемо
const product: [name: string, price: number, inStock: boolean] = ['Laptop', 1200, true];

Кортежи строго фиксируют количество элементов и тип на каждой позиции. Если вы попытаетесь присвоить значение не того типа или добавить лишний элемент — TypeScript выдаст ошибку.

Rest-элементы в кортежах

С TypeScript 4.0 появилась возможность указывать rest-элементы в кортежах. Это удобно, когда часть структуры фиксирована, а остальное — вариативно:

// Первый элемент — строка, остальные — числа
const data: [string, ...number[]] = ['scores', 95, 87, 92];

// Можно комбинировать
const logEntry: [timestamp: Date, ...messages: string[]] = [new Date(), 'info', 'user logged in'];

3. Type Alias для сложных массивов

Когда структура массива становится сложной, удобно вынести тип в отдельный alias (псевдоним). Это улучшает читаемость кода и позволяет переиспользовать тип.

type UserEntry = [id: number, name: string, email: string, isActive: boolean];

const users: UserEntry[] = [
    [1, 'Alice', 'alice@example.com', true],
    [2, 'Bob', 'bob@example.com', false]
];

// Функция, работающая с таким массивом
function printUser(user: UserEntry): void {
    const [id, name] = user;
    console.log(`#${id}: ${name}`);
}

Обратите внимание: UserEntry[] — это массив кортежей. То есть каждый элемент массива — это кортеж из 4 элементов.

4. Дженерики: максимальная гибкость

Если вы пишете функцию или класс, который должен работать с массивами произвольных типов, используйте дженерики (generics). Это не совсем «массив с несколькими типами», но позволяет создавать универсальные структуры данных.

// Функция, принимающая массив элементов любого типа
function firstElement<T>(arr: T[]): T | undefined {
    return arr[0];
}

const num = firstElement([1, 2, 3]); // тип number
const str = firstElement(['a', 'b']); // тип string

// Функция для массива из двух разных типов
function pair<A, B>(a: A, b: B): [A, B] {
    return [a, b];
}

const result = pair('hello', 42); // тип [string, number]

Дженерики особенно полезны, когда вы создаёте библиотеки или утилиты, которые должны работать с любыми типами данных, сохраняя при этом строгую типизацию.

5. Частые ошибки и как их избежать

Ошибка 1: Использование any

Самая распространённая ошибка новичков — написать const arr: any[] = [...]. Это отключает всю систему типов. Избегайте any как чумы!

// ПЛОХО — теряем все преимущества TypeScript
const bad: any[] = [1, 'hello', { a: 1 }];

// ХОРОШО — явно указываем допустимые типы
const good: (number | string | { a: number })[] = [1, 'hello', { a: 1 }];

Ошибка 2: Путаница между union и кортежем

Запомните правило: если порядок и количество элементов важны — кортеж. Если нет — union.

// Неправильно: хотели кортеж, но получили union
const coords: (number | string)[] = [10, 'y']; // работает, но не строго

// Правильно: кортеж с двумя элементами
const coords2: [number, string] = [10, 'y'];

Ошибка 3: Забыли про readonly

Если массив не должен изменяться, используйте readonly:

const config: readonly [string, number] = ['timeout', 5000];
// config[                

Блоги

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