Menu

Map в TypeScript: Map<K, V>, get, set, has и перебор

Как пользоваться Map в TypeScript: создать типизированный Map<K, V>, почему get возвращает V | undefined, set, has и delete, перебор в порядке вставки, ключи-объекты, преобразование в объект и JSON и обратно, Map против объекта и Record и типизация array.map().

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Map в TypeScript это встроенный Map из JavaScript с типизированными ключами и значениями: Map<string, number> отображает строки в числа. Создайте его через new Map<K, V>(), а затем используйте set, get, has и delete. get возвращает V | undefined, потому что ключа может не быть.

Именно аргументы типа делают Map полезным в TypeScript: каждый set проверяется, а каждый get возвращает тип значения. Последняя строка это ошибка компиляции (TS2345); @ts-expect-error позволяет блоку выполниться.

Эта страница о коллекции Map. Если вы искали array.map(), он разобран в последнем разделе.

Создание Map

Типы берутся из аргументов типа, из начальных записей или из аннотации. Начальные записи это массив кортежей [key, value] или что угодно ещё, что их выдаёт.

Две ловушки вывода типов:

  • new Map() без аргументов типа и без записей это Map<any, any>. Ничего из того, что вы кладёте и достаёте, не проверяется. Всегда пишите new Map<K, V>().
  • Записи с разными типами значений не выводят объединение. new Map([["a", 1], ["b", "x"]]) даёт ошибку TS2769 (No overload matches this call). Укажите тип: new Map<string, number | string>([...]).

get возвращает V | undefined

Map не может обещать, что ключ существует, поэтому get типизирован как V | undefined. В режиме strict нужно разобраться с undefined, прежде чем использовать значение как V:

index.ts(4,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
  Type 'undefined' is not assignable to type 'number'.

Способы исправить, от самого частого к самому редкому:

TypeScript не запоминает, что has вернул true, когда вы потом вызываете get. Проверять результат get напрямую и короче, и безопасно. Non-null утверждение, stock.get("apples")!, убирает ошибку, но не защищает, если ключа нет.

Методы Map и их типы

ЧленТип для Map<K, V>Примечания
new Map<K, V>(entries?)Map<K, V>entries: итерируемый объект из [K, V]
set(key, value)thisДобавляет или заменяет; вызовы можно связывать в цепочку
get(key)V | undefinedundefined, если ключа нет
has(key)boolean
delete(key)booleantrue, если что-то было удалено
clear()voidУдаляет всё
sizenumberСвойство, а не метод
keys(), values()итераторы по K, VРазверните в массив: [...map.keys()]
entries(), for...ofитератор по [K, V]Порядок вставки
forEach((value, key) => ...)voidОбратите внимание: значение идёт первым

Перебор Map

Map перебирается в порядке вставки. for...of по Map выдаёт кортежи [key, value] с типом [K, V].

Запись по существующему ключу обновляет значение, но сохраняет его исходную позицию в порядке.

Ключи-объекты и подсчёт

Ключом может быть любое значение, включая объекты и массивы. Ключи сравниваются как ===: два объекта с одинаковым содержимым это разные ключи. Map также стандартный способ подсчитать или сгруппировать элементы.

Чтобы использовать ключ по содержимому объекта, получите из него строковый или числовой ключ, например user.id или `${x},${y}`.

Map, объект или Record

Map<K, V>Объект / Record<string, V>
Типы ключейлюбые, сравниваются как ===string (числа становятся строками), symbol
Тип при отсутствии ключаget возвращает V | undefinedobj[key] имеет тип V, если не включена noUncheckedIndexedAccess
Порядокпорядок вставкив основном порядок вставки, но целочисленные ключи идут первыми по возрастанию
Размерmap.sizeObject.keys(obj).length
Частые добавления и удаленияоптимизирован для этогоне оптимизирован для этого
JSONне напрямую (JSON.stringify(map) даёт "{}")напрямую
Литеральный синтаксис, деструктуризациянетда
Случайные унаследованные ключинет"toString" in {} равно true

Простое правило: Map для коллекции, ключи которой являются данными (id пользователей, слова, записи кэша) и меняются во время выполнения; объектный тип или Record для фиксированного набора известных ключей и для всего, что идёт в JSON или из него. На странице словарь индексные сигнатуры, Record и Map сравниваются для поиска по строковым ключам.

Преобразование Map в объект и JSON

Записи Map не являются свойствами, поэтому JSON.stringify их не видит. Преобразуйте через Object.fromEntries и Object.entries:

JSON.parse возвращает any, поэтому as Record<...> указывает, какими данные должны быть. Это не проверка во время выполнения; прежде чем доверять такому типу, проверяйте ненадёжный JSON.

Типизация array.map()

Многие, кто ищет «typescript map», имеют в виду метод массива, который преобразует каждый элемент и возвращает новый массив. Его тип выводится из колбэка, так что аннотации нужны редко:

Аннотация возвращаемого типа колбэка ((u): Option => ...) самый понятный способ указать тип результата: пропущенное или написанное с ошибкой свойство в возвращаемом объекте тогда становится ошибкой компиляции прямо в колбэке.

Часто задаваемые вопросы

Как создать Map в TypeScript?

Передайте конструктору типы ключа и значения: const ages = new Map<string, number>(). Если есть начальные записи, типы выводятся: new Map([["ada", 36]]) это Map<string, number>. Голый new Map() без типов и без записей это Map<any, any>, который отключает проверку, поэтому всегда указывайте типы.

Почему Map.get возвращает undefined в TypeScript?

map.get(key) типизирован как V | undefined, потому что ключа может не быть. TypeScript не связывает предшествующий map.has(key) с последующим get, поэтому даже после has нужно обработать undefined: сохраните результат и проверьте его или задайте значение по умолчанию через ??.

Чем Map отличается от объекта в TypeScript?

Map принимает ключи любого типа (включая объекты), сохраняет порядок вставки, имеет size и рассчитан на частые добавления и удаления. У обычного объекта или Record<string, V> ключи только строковые (и символы), он сериализуется в JSON напрямую и поддерживает литеральный синтаксис и деструктуризацию. Используйте Map для динамических коллекций с ключами, а объект для фиксированных форм и данных JSON.

Как преобразовать Map в объект или JSON в TypeScript?

Object.fromEntries(map) превращает Map<string, V> в обычный объект, который затем можно сериализовать через JSON.stringify. JSON.stringify(map) на самом Map даёт "{}", потому что записи Map не являются свойствами. Обратное преобразование: new Map(Object.entries(obj)).

Как типизировать колбэк array.map в TypeScript?

Обычно это не нужно: items.map((item) => item.name) выводит тип item из массива, а тип результата из того, что возвращает колбэк. Чтобы задать тип результата принудительно, передайте его аргументом типа, items.map<string>(...), или аннотируйте возвращаемый тип колбэка.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ