Avatar ·

Как правильно перебирать (итерировать) Map в TypeScript? Все способы и примеры

Всем привет! Я только начал осваивать TypeScript и столкнулся с проблемой при работе с Map. Я знаю, что Map — это удобная коллекция для хранения пар ключ-значение, но никак не могу понять, как правильно её перебирать. В JavaScript я привык использовать `for...of` для массивов, но с Map всё как-то запутанно. Пробовал разные варианты, но TypeScript ругается на типы или выдаёт не то, что я ожидаю. Вот пример моего кода: ```typescript const userScores = new Map(); userScores.set('Alice', 95); userScores.set('Bob', 87); userScores.set('Charlie', 92); // Пытаюсь перебрать — не работает for (const entry of userScores) { console.log(entry); // Что тут будет? Массив? Объект? } // Пробовал через .entries() — тоже непонятно const entries = userScores.entries(); for (const [key, value] of entries) { console.log(key, value); // Ругается на типы } ``` Подскажите, пожалуйста: 1. Какие существуют способы итерации по Map в TypeScript? 2. Как правильно деструктуризировать ключи и значения? 3. Есть ли разница между `for...of`, `forEach` и методами `.keys()`, `.values()`, `.entries()`? 4. Как TypeScript помогает с типами при итерации? Буду благодарен за подробные примеры с объяснениями. Заранее спасибо! <<>>

Полный гайд по итерации Map в TypeScript

Отличный вопрос! Map в TypeScript (и JavaScript) — это итерируемая коллекция, но с некоторыми особенностями. Давайте разберём все способы перебора с правильной типизацией.

1. Базовая итерация через for...of

Map по умолчанию итерируется по записям (entries). Каждый элемент — это массив [key, value]. TypeScript автоматически выводит тип кортежа:

const userScores = new Map<string, number>([
    ['Alice', 95],
    ['Bob', 87]
]);

// Простая итерация — entry имеет тип [string, number]
for (const entry of userScores) {
    console.log(entry[0], entry[1]); // Alice 95, Bob 87
}

// Лучший вариант — деструктуризация
for (const [key, value] of userScores) {
    console.log(`${key}: ${value}`);
}

2. Использование .entries()

Метод .entries() возвращает тот же самый итератор, что и for...of по умолчанию. Разницы нет:

// Полностью эквивалентно предыдущему примеру
for (const [key, value] of userScores.entries()) {
    console.log(key, value);
}

3. Итерация только по ключам (.keys()) или значениям (.values())

Если вам нужны только ключи или только значения:

// Только ключи — тип string
for (const key of userScores.keys()) {
    console.log(`Ключ: ${key}`);
}

// Только значения — тип number
for (const value of userScores.values()) {
    console.log(`Значение: ${value}`);
}

4. Метод forEach() — функциональный подход

У Map есть встроенный метод forEach, который принимает callback с тремя параметрами (значение, ключ, сам Map):

userScores.forEach((value, key) => {
    console.log(`${key} набрал ${value} баллов`);
});

// Если нужен доступ к самому Map (третий параметр):
userScores.forEach((value, key, map) => {
    console.log(`Размер Map: ${map.size}`);
    map.set(key, value + 5); // изменяем значения
});

5. Преобразование в массив

Иногда удобно превратить Map в массив для дальнейшей обработки:

// Массив записей [key, value]
const entriesArray = Array.from(userScores);
// или
const entriesArray2 = [...userScores];

// Массив только ключей
const keysArray = [...userScores.keys()];

// Массив только значений
const valuesArray = [...userScores.values()];

// Пример с filter
const highScorers = [...userScores].filter(([_, score]) => score > 90);
console.log(highScorers); // [['Alice', 95], ['Charlie', 92]]

6. Важные моменты по типам

TypeScript строго следит за типами при итерации:

const mixedMap = new Map<number, string | boolean>();
mixedMap.set(1, 'hello');
mixedMap.set(2, true);

// TypeScript знает: key — number, value — string | boolean
for (const [key, value] of mixedMap) {
    if (typeof value === 'string') {
        console.log(value.toUpperCase()); // Без ошибок
    }
}

7. Когда что использовать?

  • for...of с деструктуризацией — самый читаемый и типизированный способ для базового перебора
  • forEach — когда нужен функциональный стиль или доступ к самому Map
  • .keys() / .values() — если нужна только одна из составляющих
  • Spread-оператор — для преобразования в массив и цепочек методов (map, filter, reduce)

Надеюсь, теперь всё стало понятно! Map в TypeScript — мощный инструмент, а правильная итерация делает работу с ним удобной и безопасной.

<<>>

Альтернативный подход: итерация через Iterator и Symbol.iterator

Если вы хотите глубже понять, как работает итерация в TypeScript, можно использовать протокол итератора напрямую. Это особенно полезно, когда нужно вручную управлять процессом перебора или реализовать ленивую итерацию.

const map = new Map<string, number>([
    ['a', 1],
    ['b', 2],
    ['c', 3]
]);

// Получаем итератор вручную
const iterator = map[Symbol.iterator]();

// Ручной перебор
let result = iterator.next();
while (!result.done) {
    const [key, value] = result.value;
    console.log(key, value);
    result = iterator.next();
}

// Или более кратко с деструктуризацией
for (let [key, value] of map) {
    // Можно прервать цикл досрочно
    if (key === 'b') break;
    console.log(key, value);
}

Когда это может пригодиться?

  • Реализация кастомных коллекций, совместимых с for...of
  • Ленивая генерация данных (например, бесконечные последовательности)
  • Тонкая настройка поведения итерации (пропуск элементов, изменение порядка)

Однако в повседневной работе достаточно использовать for...of или forEach, описанные в основном ответе. Прямая работа с итератором — это уже уровень "продвинутого" использования.