Что такое Record в TypeScript и как его правильно использовать?
Что такое Record в TypeScript?
Record<K, T> — это встроенный утилитарный тип, который создаёт объектный тип, где все ключи имеют тип K, а все значения — тип T. По сути, это синтаксический сахар над индексной сигнатурой { [key: K]: T }, но с важными отличиями.
Чем Record отличается от обычной сигнатуры?
Главное отличие — Record позволяет задать конкретное множество ключей (через union-тип), а не просто "любую строку". Это даёт строгую типизацию и автодополнение.
Рассмотрим вашу задачу со статусами заказа. Вот правильное решение:
type OrderStatus = 'pending' | 'approved' | 'rejected';
const statusColors: Record<OrderStatus, string> = {
pending: 'yellow',
approved: 'green',
rejected: 'red'
};
// TypeScript теперь знает все ключи:
statusColors.pending; // ✅ OK
statusColors['approved']; // ✅ OK
// statusColors.unknown; // ❌ Ошибка: нет такого ключа
Когда использовать Record?
- Маппинг конечного набора ключей — как в примере со статусами, где ключи известны заранее.
- Словари с однотипными значениями — например, конфиги, переводы (i18n), кэши.
- Типизация ответов API — когда сервер возвращает объект с динамическими ключами, но значениями одного типа.
Record vs Index Signature
// Index signature — разрешает ЛЮБЫЕ строковые ключи
type UserMap = { [key: string]: User };
const map1: UserMap = {};
map1['anything'] = new User(); // ✅ Работает, но небезопасно
// Record с конкретным union — строгие ключи
type UserRoles = 'admin' | 'user' | 'guest';
const rolePermissions: Record<UserRoles, string[]> = {
admin: ['create', 'delete'],
user: ['read'],
guest: ['read']
};
// rolePermissions['superadmin'] // ❌ Ошибка компиляции
Record vs Map
Map — это JS-коллекция из ES6, которая:
- Поддерживает любые типы ключей (объекты, функции).
- Имеет методы
.get(),.set(),.has(). - Сохраняет порядок вставки.
Record — это просто TypeScript-тип для обычных объектов. Используйте Record, когда:
- Ключи — строки или числа.
- Нужна строгая проверка на этапе компиляции.
- Не нужны методы Map (достаточно скобочной нотации).
Продвинутые примеры
// Record с вложенными типами
type Config = Record<string, { enabled: boolean; timeout: number }>;
// Частичный Record (не все ключи обязательны)
type PartialRecord<K extends string, T> = Partial<Record<K, T>>;
// Record для enum
enum Direction {
Up = 'UP',
Down = 'DOWN'
}
const directionLabels: Record<Direction, string> = {
[Direction.Up]: 'Вверх',
[Direction.Down]: 'Вниз'
};
Итог
Record — идеальный выбор, когда вам нужен объект с известным набором ключей (union) и однотипными значениями. Он даёт безопасность типов, автодополнение в IDE и читаемый код. Для динамических ключей с неизвестным набором используйте обычную индексную сигнатуру, а для сложной логики с ключами-объектами — Map.
Альтернативный подход: использование утилитарных типов вместе с Record
Иногда полезно комбинировать Record с другими встроенными типами для более гибкой типизации.
Partial + Record для опциональных ключей
Если не все ключи обязательны, используйте Partial<Record<K, T>>:
type Status = 'pending' | 'approved' | 'rejected';
const optionalColors: Partial<Record<Status, string>> = {
pending: 'yellow'
// approved и rejected могут отсутствовать
};
Readonly + Record для неизменяемых конфигов
type ColorMap = Readonly<Record<Status, string>>;
const COLORS: ColorMap = {
pending: 'yellow',
approved: 'green',
rejected: 'red'
};
// COLORS.pending = 'blue'; // ❌ Ошибка: только для чтения
Record с шаблонными литералами
Можно создавать сложные ключи с помощью Template Literal Types:
type EventName = `on${Capitalize<string>}`;
type EventHandlers = Partial<Record<EventName, () => void>>;
const handlers: EventHandlers = {
onClick: () => console.log('click'),
onHover: () => console.log('hover')
};
Этот подход особенно полезен для типизации обработчиков событий, CSS-свойств или конфигураций с префиксами. Главное — помнить, что Record — это строительный блок, который можно комбинировать с другими утилитарными типами для достижения нужной семантики.
Ответов пока нет.
Войдите чтобы оставить ответ