Как динамически добавлять свойства объекту в TypeScript без ошибок типов?
Решение с помощью Record и Index Signatures
Проблема, с которой вы столкнулись, типична для новичков в TypeScript. Дело в том, что пустой объект {} имеет тип {} — это объект без свойств. TypeScript строго проверяет, что вы не обращаетесь к несуществующим полям. Чтобы решить эту задачу, нужно явно указать, что объект может содержать динамические свойства. Есть несколько корректных подходов.
1. Использование Record (рекомендуемый способ)
Record<K, V> — это встроенный тип, который создаёт объект с ключами типа K и значениями типа V.
// Объявляем объект, где ключи — строки, значения — любые (string | number)
const user: Record<string, string | number> = {};
if (someCondition) {
user.name = 'Алексей'; // ✅ Работает
}
if (anotherCondition) {
user.age = 30; // ✅ Работает
}
console.log(user.name); // 'Алексей'
console.log(user.age); // 30
Плюсы: Простота, читаемость, типизация значений. Минус: Все ключи должны быть одного типа (в примере — string | number).
2. Использование Index Signature (сигнатура индекса)
Можно указать, что объект может иметь любое количество свойств с определённым типом значения.
interface User {
[key: string]: string | number | undefined;
}
const user: User = {};
if (someCondition) {
user.name = 'Алексей'; // ✅
}
if (anotherCondition) {
user.age = 30; // ✅
}
[key: string]: ... означает: «объект может иметь свойства с любыми строковыми ключами». undefined добавлен, чтобы можно было проверять существование свойства: if (user.name !== undefined).
3. Частичное использование (Partial) с заранее известными полями
Если вы знаете, какие свойства могут появиться, но не все сразу, используйте Partial<T>.
interface UserProfile {
name: string;
age: number;
email: string;
}
const user: Partial<UserProfile> = {};
if (someCondition) {
user.name = 'Алексей'; // ✅
}
if (anotherCondition) {
user.age = 30; // ✅
}
// user.email остаётся undefined, но это нормально
4. Тип any (не рекомендуется, но для справки)
const user: any = {};
user.name = 'Алексей'; // ✅, но теряется проверка типов
Что выбрать?
- Если ключи и типы значений заранее неизвестны — используйте
Record<string, any>или{[key: string]: any}. Но лучше избегатьany. - Если типы значений разные (строка, число, boolean) —
Record<string, string | number | boolean>. - Если вы знаете структуру, но не уверены, какие поля будут —
Partial<YourInterface>. - Если вам нужна максимальная гибкость — index signature с
unknownи проверками:{[key: string]: unknown}.
Бонус: безопасное чтение динамических свойств
Чтобы избежать ошибок при чтении, используйте проверку:
if ('name' in user) {
console.log(user.name); // TypeScript знает, что свойство существует
}
// Или через Optional Chaining
console.log(user?.name); // undefined, если нет
Теперь ваш код будет работать без ошибок типов, а TypeScript продолжит защищать вас от опечаток и неверных типов значений.
<<Альтернативный подход: использование Map
Если ваша задача — динамически добавлять свойства, где ключи — строки, а значения — любого типа, возможно, вам подойдёт встроенная структура Map. Это не объект, но часто решает ту же задачу с лучшей типобезопасностью.
// Создаём Map, где ключи — строки, значения — string | number
const user = new Map<string, string | number>();
if (someCondition) {
user.set('name', 'Алексей'); // ✅
}
if (anotherCondition) {
user.set('age', 30); // ✅
}
// Чтение
console.log(user.get('name')); // 'Алексей' | undefined
console.log(user.get('age')); // 30 | undefined
Преимущества Map перед объектом:
- Типизация и ключей, и значений.
- Методы
.set(),.get(),.has(),.delete()— удобнее для динамической работы. - Нет проблем с прототипом (например,
toStringне будет мешать). - Итерация через
for...ofили.forEach().
Недостаток: Это не объект, поэтому если вам нужен именно объект (например, для JSON.stringify или передачи в библиотеку, ожидающую объект), придётся конвертировать:
const obj = Object.fromEntries(user);
console.log(obj); // { name: 'Алексей', age: 30 }
Если ваша задача — просто хранить динамические данные с типизацией, Map — отличный выбор.