Перейти к основному содержимому

Коллекции и массивы в TypeScript

Разработчику Архитектору

Дальше: Дженерики · Объекты и классы · Функции · Справочник — составные типы


JavaScript даёт массивы, Map, Set и обычные объекты-словари. TypeScript описывает элементы и ключи так, чтобы map, filter и доступ по индексу не превращались в any. Здесь — выбор структуры и типичные ошибки, без копирования всех таблиц из 301.

Маршрут: Типы и типизацияколлекциидженерикиклассы.

Runtime-поведение массивов и итераторов — типы в JavaScript. Циклы for13.md.


Карта структур данных

СтруктураТип TSКогда выбирать
Список однотипных значенийT[] / Array<T>порядок, индекс, map
Фиксированная позиция и типкортеж [A, B]координаты, [data, err]
Словарь с известными ключамиRecord<K, V>роли, статусы, конфиг
Динамические ключиMap<K, V>ключ не только string
Уникальные значенияSet<T>дедупликация, membership
Слабые ссылки на объектыWeakMap, WeakSetкэш без утечки

Массивы: T[] и Array<T>

const tags: string[] = ["ts", "js"];
const scores: Array<number> = [10, 20, 30];

function last<T>(items: T[]): T | undefined {
return items[items.length - 1];
}

const lastTag = last(tags); // string | undefined

Разбор:

  • string[] и Array<string> эквивалентны для чтения.
  • Индекс за границей даёт T | undefined при noUncheckedIndexedAccess6.md.
  • Пустой массив [] без аннотации выводится как never[] — укажите тип явно: const ids: string[] = [].

Readonly-массивы

const frozen: readonly number[] = [1, 2, 3];
// frozen.push(4); // ошибка

type ReadonlyNums = ReadonlyArray<number>;

Разбор:

  • readonly запрещает мутацию методов push, sort (in-place), но не делает элементы глубоко неизменяемыми.
  • Для вложенных структур нужен Readonly на уровне полей — 10.md.

Кортежи (tuples)

Кортеж — массив фиксированной длины с типом на каждой позиции:

type Rgb = [number, number, number];
const red: Rgb = [255, 0, 0];

type HttpLine = [status: number, message: string];
const ok: HttpLine = [200, "OK"];

Labeled tuples (именованные элементы)

Метки [x: number, y: number] не попадают в runtime — это подсказки для IDE и документация позиций. Длина и типы по индексам по-прежнему проверяются жёстко:

type Point2D = [x: number, y: number];
type Point3D = [x: number, y: number, z: number];

const cursor: Point2D = [320, 240];
const vertex: Point3D = [1.0, 0.5, -1.0];

// const bad2d: Point2D = [320, 240, 0];
// ошибка: source has 3 element(s) but target allows only 2

// const bad3d: Point3D = [320, 240];
// ошибка: source has 2 element(s) but target requires 3

Разбор:

  • Point2D и number[] — разные контракты: кортеж фиксирует длину, массив — нет.
  • При деструктуризации const [x, y] = cursor типы x и ynumber; имена из типа в подсказках при наведении на cursor[0].

Опциональный элемент и rest:

type HttpResponse = [code: number, message?: string, ...headers: string[]];

const r1: HttpResponse = [404];
const r2: HttpResponse = [200, "OK", "Content-Type: text/plain"];

Разбор:

  • Именованные элементы кортежа (status:) — только для подсказок, в runtime их нет.
  • Rest ...headers собирает оставшиеся элементы в массив string[].
  • Кортеж [number, number] фиксирует длину и позиции; number[] допускает любое количество элементов.

as const и кортежи-литералы

const point = [10, 20] as const;
// readonly [10, 20] — литеральные типы

type PointTuple = typeof point;

Разбор:

  • as const сужает типы до литералов и добавляет readonly.
  • Удобно для списков маршрутов, ключей — связка с typeof в 10.md.

Record<K, V>

Словарь, где ключи — подмножество строк (часто union), а значения одного типа:

type Role = "admin" | "user" | "guest";

type Permissions = Record<Role, string[]>;

const perms: Permissions = {
admin: ["read", "write", "delete"],
user: ["read"],
guest: [],
};

Разбор:

  • TypeScript проверит, что все ключи Role присутствуют.
  • Лишний ключ в литерале — excess property check (как у объектов).

Индексная сигнатура для произвольных строк:

type StringDict = Record<string, number>;

const scores: StringDict = {};
scores["alice"] = 10;

Map<K, V> и Set<T>

interface User {
id: string;
name: string;
}

const byId = new Map<string, User>();
byId.set("u1", { id: "u1", name: "Ann" });

function getOrThrow(map: Map<string, User>, id: string): User {
const user = map.get(id);
if (!user) throw new Error(`User not found: ${id}`);
return user;
}

const uniqueTags = new Set<string>(["ts", "ts", "js"]);
uniqueTags.add("node");

Разбор:

RecordMap
Ключив основном string / unionлюбой тип (object, number)
Порядокне гарантирован для всех операцийпорядок вставки
РазмерObject.keys.size
Сериализация JSONестественнанужен Array.from(map)

Set — проверка уникальности и быстрый has:

function unique<T>(items: T[]): T[] {
return [...new Set(items)];
}

WeakMap и WeakSet

const meta = new WeakMap<object, { visited: boolean }>();

function mark(o: object): void {
meta.set(o, { visited: true });
}

Разбор:

  • Ключи только объекты; не итерируются целиком.
  • Не удерживают объект от GC — метаданные исчезают вместе с ключом.
  • Типы: WeakMap<K extends object, V>, WeakSet<T extends object>Справочник — составные типы.

Методы массивов и вывод типов

const nums = [1, 2, 3];
const doubled = nums.map((n) => n * 2); // number[]
const evens = nums.filter((n) => n % 2 === 0); // number[]

const sum = nums.reduce((acc, n) => acc + n, 0); // number

С пользовательским типом:

interface Product {
sku: string;
price: number;
}

const catalog: Product[] = [
{ sku: "a", price: 10 },
{ sku: "b", price: 20 },
];

const total = catalog.reduce((sum, p) => sum + p.price, 0);
const skus = catalog.map((p) => p.sku);

Разбор:

  • Тип acc в reduce выводится из начального значения (0 → number).
  • Явная аннотация нужна, если начальное значение шире: reduce<number>(..., 0).

flatMap:

const lines = ["a b", "c"].flatMap((line) => line.split(" "));
// string[]

Кортеж как результат: Promise и ошибки

Результат в виде union: либо поле data, либо поле error — без отдельного класса:

type Result<T> = [error: null, data: T] | [error: Error, data: null];

async function loadSafe(): Promise<Result<string>> {
try {
const data = await fetchText();
return [null, data];
} catch (e: unknown) {
return [e instanceof Error ? e : new Error(String(e)), null];
}
}

Разбор:

  • Именованные элементы кортежа документируют позиции.
  • Для сложных сценариев чаще используют discriminated union — 10.md, 17.md.

Массивы в generic-коде

function first<T>(items: readonly T[]): T | undefined {
return items[0];
}

function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}

Подробнее о параметрах типа — 24.md.


Совместимость readonly и мутабельных массивов

function sortInPlace(nums: number[]): void {
nums.sort();
}

const readonlyNums: readonly number[] = [3, 1, 2];
// sortInPlace(readonlyNums); // ошибка без копии

sortInPlace([...readonlyNums]);

Разбор:

  • Мутабельный параметр не принимает readonly безопасно.
  • Spread создаёт новый массив для изменения.

JSON и коллекции

JSON.parse возвращает any (или unknown при настройках). Для массивов:

function parseStringArray(raw: unknown): string[] {
if (!Array.isArray(raw)) throw new Error("Expected array");
if (!raw.every((x) => typeof x === "string")) {
throw new Error("Expected string[]");
}
return raw;
}

Не утверждайте as string[] без проверки — 6.md.


Частые ошибки

ОшибкаПричинаЧто делать
never[] у const a = []нет контекста типаconst a: Item[] = []
Кортеж vs массивпутаница длины[A,B] только для фикс. позиций
Record без всех ключей unionнеполный объектдобавить ключи или Partial
map.get без проверкиundefinedguard или getOrThrow
Мутация readonlypush/sortкопия [...arr]
as T[] на JSONневерные данныеruntime guard

Практика

  1. Опишите Record<HttpStatus, string> для кодов 200, 404, 500 и сообщений.
  2. Реализуйте unique<T>(items: T[]) через Set.
  3. Храните пользователей в Map<string, User> и напишите getOrThrow.
  4. Создайте кортеж Rgb и функцию toCss([r,g,b]) с типом string.
  5. Опишите Point2D / Point3D с labeled tuples и убедитесь, что лишний элемент даёт ошибку компиляции.
  6. Пройдите catalog.reduce с явным типом аккумулятора, чтобы найти самый дорогой Product.

Смежные статьи