Словари в TypeScript: от хаоса к порядку (полное руководство с кодом)
Представьте, что вы пишете приложение для изучения английского языка. Вам нужно хранить переводы слов: "cat" → "кошка", "dog" → "собака". Как лучше всего организовать эти данные? Конечно, использовать словарь (Dictionary) — структуру данных, где каждый ключ уникален и связан со значением.
В JavaScript словари реализуются через обычные объекты или Map. Но TypeScript добавляет мощную типизацию, превращая хаотичные коллекции в строгие, предсказуемые структуры. В этой статье мы разберём объявление и инициализацию Dictionary в TypeScript от простого к сложному. Вы узнаете, как избежать типичных ошибок и писать код, который работает предсказуемо.
Что такое Dictionary в TypeScript? (И почему это не просто объект)
В программировании Dictionary (словарь) — это коллекция пар «ключ-значение». В TypeScript вы можете создать его тремя основными способами:
- Объект с сигнатурой индекса (
{ [key: string]: T }) - Record — утилитарный тип
- Map — встроенный ES6 класс
Каждый подход имеет свои плюсы и нюансы. Давайте разберём их на реальных примерах.
Способ 1: Классический объект с сигнатурой индекса
Самый популярный способ объявить словарь в TypeScript — использовать сигнатуру индекса. Это говорит компилятору: «Объект может иметь любое количество свойств с ключами определённого типа».
// Объявляем тип словаря, где ключ — строка, значение — число
interface DictionaryNumber {
[key: string]: number;
}
// Инициализация
const ages: DictionaryNumber = {
"Анна": 25,
"Пётр": 30,
"Елена": 28
};
// Добавление нового элемента
ages["Ольга"] = 35;
console.log(ages["Анна"]); // 25
Важно: TypeScript требует, чтобы все значения соответствовали объявленному типу. Попытка добавить строку вызовет ошибку компиляции:
// Ошибка! Тип 'string' не может быть присвоен типу 'number'
ages["Ольга"] = "тридцать пять";
Когда использовать этот подход?
- Когда нужен простой и быстрый словарь
- Когда ключи — строки или числа
- Для небольших коллекций (до 1000 элементов)
Способ 2: Record — элегантный синтаксис
TypeScript предоставляет встроенный утилитарный тип Record<K, V>. Он создаёт тип объекта, где все ключи имеют тип K, а значения — V.
// Объявление и инициализация в одной строке
const grades: Record<string, number> = {
"Математика": 5,
"Физика": 4,
"История": 5
};
// Можно использовать enum для ключей
enum Subject {
Math = "math",
Physics = "physics",
History = "history"
}
const gradesBySubject: Record<Subject, number> = {
[Subject.Math]: 5,
[Subject.Physics]: 4,
[Subject.History]: 5
};
Преимущества Record:
- Краткий синтаксис
- Хорошая читаемость
- Поддержка union-типов для ключей
Способ 3: Map — для продвинутых сценариев
Класс Map из ES6 — это полноценная коллекция «ключ-значение» с дополнительными возможностями. В TypeScript его типизируют через дженерики:
// Объявление Map
const userRoles: Map<string, string> = new Map([
["admin", "Администратор"],
["user", "Пользователь"],
["guest", "Гость"]
]);
// Добавление
userRoles.set("moderator", "Модератор");
// Получение
console.log(userRoles.get("admin")); // "Администратор"
// Проверка наличия
if (userRoles.has("user")) {
console.log("Роль существует");
}
// Итерация
userRoles.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
Когда Map лучше объекта?
- Когда ключи не только строки (например, объекты или символы)
- Когда нужна гарантированная сохранность порядка вставки
- Когда часто удаляете/добавляете элементы
- Когда нужны методы
.size,.clear(),.delete()
Как правильно инициализировать пустой словарь?
Часто возникает ситуация: нужно объявить словарь, но наполнить его данными позже. Вот правильные паттерны:
// Для объекта
const emptyDict: Record<string, number> = {};
// Для Map
const emptyMap = new Map<string, number>();
// Добавление позже
emptyDict["key1"] = 100;
emptyMap.set("key2", 200);
Ошибка новичка:
// Некорректно! TypeScript выдаст ошибку
const dict: Record<string, number> = {};
dict["test"] = "строка"; // Ошибка
Типизированные словари с пользовательскими типами
Словари могут хранить не только примитивы, но и сложные объекты:
interface Product {
id: number;
name: string;
price: number;
}
// Словарь товаров по ID
const products: Record<number, Product> = {
1: { id: 1, name: "Ноутбук", price: 50000 },
2: { id: 2, name: "Мышь", price: 1500 }
};
// Добавление нового товара
products[3] = { id: 3, name: "Клавиатура", price: 3000 };
// Обход словаря
Object.values(products).forEach(product => {
console.log(`${product.name}: ${product.price}₽`);
});
5 частых ошибок при работе со словарями в TypeScript
Даже опытные разработчики иногда попадаются на этих граблях:
- Забыть про undefined — при обращении к несуществующему ключу объекта вы получите
undefined. Всегда проверяйте:
const dict: Record<string, number> = { "a": 1 };
const value = dict["b"]; // undefined, но тип number!
// Лучше: const value = dict["b"] ?? 0;
- Мутация через индексацию — TypeScript не всегда ловит опечатки в ключах. Используйте
as constдля строгих ключей:
const strictDict = { "apple": 5, "banana": 3 } as const