Использование объектов TypeScript в качестве словарей (Dictionary) как в C#
Основной подход: использование index signature
В TypeScript для создания словарей используется index signature (сигнатура индекса). Это именно то, что вы уже попробовали:
let dict: { [key: string]: number } = {};
Такая запись означает: «объект, у которого ключи — строки, а значения — числа». Для вашего случая с классом User:
class User {
constructor(public name: string, public age: number) {}
}
let users: { [key: string]: User } = {};
users["alice"] = new User("Alice", 30);
users["bob"] = new User("Bob", 25);
Более удобная альтернатива: Record<K, V>
TypeScript предоставляет встроенный тип Record<Keys, Type>, который делает код чище:
let users: Record<string, User> = {};
users["alice"] = new User("Alice", 30);
Запись Record<string, User> эквивалентна { [key: string]: User }, но читается проще.
Проверка существования ключа
Есть два основных способа:
- Оператор
in— проверяет наличие ключа в объекте (включая прототип):
if ("alice" in users) {
console.log(users["alice"].name);
}
- hasOwnProperty — проверяет только собственные свойства (рекомендуется):
if (users.hasOwnProperty("alice")) {
console.log(users["alice"].name);
}
- Optional chaining (ES2020+) — современный способ:
const user = users["alice"]; // если ключа нет, user будет undefined
console.log(user?.name); // безопасный доступ
Перебор элементов словаря
Несколько вариантов для разных задач:
// 1. Только значения
for (const key in users) {
if (users.hasOwnProperty(key)) {
console.log(users[key].name);
}
}
// 2. Ключи и значения через Object.keys
Object.keys(users).forEach(key => {
console.log(`Ключ: ${key}, Имя: ${users[key].name}`);
});
// 3. Использование Object.entries (ES2017+)
for (const [key, user] of Object.entries(users)) {
console.log(`${key}: ${user.name}, ${user.age} лет`);
}
// 4. Только значения через Object.values
Object.values(users).forEach(user => {
console.log(user.name);
});
Удаление элемента
delete users["alice"];
Полноценный класс Dictionary
Если вам нужны методы, как в C# (Add, ContainsKey, Remove), можно создать свой класс:
class Dictionary<TKey, TValue> {
private items: Record<string, TValue> = {};
add(key: TKey, value: TValue): void {
const strKey = String(key);
if (this.items.hasOwnProperty(strKey)) {
throw new Error(`Ключ "${strKey}" уже существует`);
}
this.items[strKey] = value;
}
get(key: TKey): TValue | undefined {
return this.items[String(key)];
}
containsKey(key: TKey): boolean {
return this.items.hasOwnProperty(String(key));
}
remove(key: TKey): boolean {
const strKey = String(key);
if (this.items.hasOwnProperty(strKey)) {
delete this.items[strKey];
return true;
}
return false;
}
get count(): number {
return Object.keys(this.items).length;
}
*[Symbol.iterator](): Iterator<[string, TValue]> {
for (const [key, value] of Object.entries(this.items)) {
yield [key, value];
}
}
}
// Использование:
const dict = new Dictionary<string, User>();
dict.add("alice", new User("Alice", 30));
console.log(dict.containsKey("alice")); // true
console.log(dict.get("alice")?.name); // Alice
Важные замечания
- Тип ключа — в index signature можно использовать только
string,numberилиsymbol. Для других типов придётся приводить к строке. - Безопасность — всегда проверяйте наличие ключа перед доступом, иначе получите
undefined. - Производительность — для больших словарей (сотни тысяч элементов) рассмотрите
Map(см. ответ 2).
Альтернатива: использование Map вместо объектов
В JavaScript (и TypeScript) есть встроенный класс Map, который часто удобнее обычных объектов для работы со словарями:
let userMap = new Map<string, User>();
userMap.set("alice", new User("Alice", 30));
userMap.set("bob", new User("Bob", 25));
// Проверка наличия
console.log(userMap.has("alice")); // true
// Получение значения
const user = userMap.get("alice"); // User | undefined
// Удаление
userMap.delete("alice");
// Перебор
userMap.forEach((value, key) => {
console.log(`${key}: ${value.name}`);
});
// Размер
console.log(userMap.size); // количество элементов
Когда выбирать Map вместо объекта
- Ключи — не строки — Map поддерживает любые типы ключей (объекты, функции, числа).
- Частые операции вставки/удаления — Map оптимизирован для таких сценариев.
- Нужен порядок элементов — Map сохраняет порядок вставки, объекты — нет (хотя современные браузеры сохраняют порядок строковых ключей).
- Нужен метод
size— для объектов придётся использоватьObject.keys(obj).length. - Безопасность прототипа — Map не имеет прототипа, поэтому нет проблем с ключами типа
toStringилиconstructor.
Недостатки Map
- Нет поддержки JSON-сериализации напрямую (нужно преобразовывать).
- Синтаксис чуть более многословный (
.set(),.get()вместо квадратных скобок). - Для простых случаев с однотипными строковыми ключами объекты проще.
Рекомендация: для вашей задачи с пользователями отлично подойдёт Map<string, User>. Он даёт чёткий API, как в C#, и избавляет от необходимости писать проверки hasOwnProperty.