Как правильно использовать Map из ES6 в TypeScript? Типизация и примеры
Основы типизации Map в TypeScript
В TypeScript Map — это дженерик, который принимает два параметра типа: тип ключа и тип значения. Базовая запись выглядит так:
const map = new Map<string, number>();
map.set('key', 42); // OK
map.set(123, 'value'); // Ошибка: ключ должен быть string, значение — number
Решение вашей задачи
Для хранения объектов с полями id и name:
interface User {
id: number;
name: string;
}
const userMap = new Map<string, User>();
userMap.set('john', { id: 1, name: 'John Doe' });
userMap.set('jane', { id: 2, name: 'Jane Smith' });
// Получение значения
const user = userMap.get('john');
if (user) {
console.log(user.name); // TypeScript знает, что это User
}
Работа с разными типами ключей и значений
Если нужно хранить значения разных типов, используйте объединения (union types) или any с осторожностью:
// Строгий вариант с union
const mixedMap = new Map<string, string | number | boolean>();
mixedMap.set('name', 'Alice');
mixedMap.set('age', 30);
mixedMap.set('active', true);
// Или с конкретными типами через keyof
type ValueType = {
name: string;
age: number;
active: boolean;
};
const typedMap = new Map<keyof ValueType, ValueType[keyof ValueType]>();
typedMap.set('name', 'Bob');
typedMap.set('age', 25);
Объекты и функции как ключи
TypeScript поддерживает использование объектов и функций в качестве ключей, но нужно учитывать особенности:
// Объекты как ключи
const objKeyMap = new Map<object, string>();
const key1 = { id: 1 };
const key2 = { id: 2 };
objKeyMap.set(key1, 'value1');
objKeyMap.set(key2, 'value2');
// Функции как ключи
const fnMap = new Map<Function, string>();
function foo() {}
fnMap.set(foo, 'function foo');
// Важно: ключи сравниваются по ссылке, не по значению!
const sameKey = { id: 1 };
console.log(objKeyMap.get(sameKey)); // undefined — другой объект!
Best practices
- Всегда указывайте типы — это главное преимущество TypeScript
- Используйте интерфейсы для сложных типов значений
- Проверяйте наличие ключа через
has()или optional chaining - Для readonly Map используйте
ReadonlyMap - Избегайте
any— это убивает смысл типизации
// Пример с ReadonlyMap
function processData(map: ReadonlyMap<string, User>) {
// map.set() — ошибка компиляции, только чтение
const user = map.get('john');
}
// Итерация по Map
for (const [key, value] of userMap) {
console.log(`${key}: ${value.name}`);
}
// Преобразование в массив
const entries = Array.from(userMap.entries());
const keys = Array.from(userMap.keys());
Обработка отсутствующих значений
// Безопасное получение с default
const user = userMap.get('unknown') ?? { id: 0, name: 'Default' };
// Или через has()
if (userMap.has('john')) {
const user = userMap.get('john')!; // Non-null assertion (осторожно!)
}
Используя эти подходы, вы получите строгую типизацию и избежите ошибок времени выполнения. TypeScript будет проверять соответствие типов на этапе компиляции.
<<Альтернативный подход: Record и утилитарные типы
В некоторых случаях вместо Map удобнее использовать встроенный тип Record или объекты с индексацией. Это особенно актуально, когда ключи — строки или числа, и не нужны методы Map (например, итерация по порядку вставки).
// Record вместо Map
type UserMap = Record<string, User>;
const users: UserMap = {
'john': { id: 1, name: 'John' },
'jane': { id: 2, name: 'Jane' }
};
// Или с Partial для опциональных значений
type OptionalUserMap = Partial<Record<string, User>>;
const optionalUsers: OptionalUserMap = {
'john': { id: 1, name: 'John' }
// 'jane' может отсутствовать
};
Когда использовать Record вместо Map
- Ключи — простые строки или числа
- Не нужны методы
Map(clear, delete, size) - Нужна сериализация в JSON (Map не сериализуется напрямую)
- Производительность: объекты быстрее для простых операций
Комбинированный подход
Можно использовать Map для хранения, а для передачи данных преобразовывать в Record:
function mapToRecord<K extends string, V>(map: Map<K, V>): Record<K, V> {
const record = {} as Record<K, V>;
for (const [key, value] of map) {
record[key] = value;
}
return record;
}
Этот подход сохраняет все преимущества Map (произвольные ключи, методы) и позволяет легко интегрироваться с API, ожидающими объекты.