Record в TypeScript: что это и как использовать новичку

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Record в TypeScript: разбираемся на практике

Представьте, что вы пишете приложение для изучения английских слов. Вам нужно хранить переводы: cat → кошка, dog → собака. Как это правильно затипизировать в TypeScript? Можно создать интерфейс, но что, если ключи заранее неизвестны? Или их слишком много? Тут на помощь приходит Record — один из самых полезных, но часто недооценённых утилитарных типов.

В этой статье мы разберём, что такое Record type в TypeScript, на реальных примерах, которые вы сможете сразу применить в своих проектах. Никакой скучной теории — только практика.

Что такое Record в TypeScript? Простыми словами

Record<K, V> — это встроенный утилитарный тип, который создаёт объект, где все ключи имеют тип K, а все значения — тип V.
Думайте о нём как о типизированном словаре или карте с фиксированным набором ключей.

Базовая запись выглядит так:

// Обычный объект
const userRoles: Record<string, string> = {
  admin: 'Администратор',
  user: 'Пользователь',
  guest: 'Гость'
};

Здесь Record<string, string> означает: «Объект, где ключи — строки, и значения — тоже строки». Просто и понятно.

Чем Record лучше обычного интерфейса?

  • Гибкость ключей: Вы можете задать конкретный набор ключей через union-тип.
  • Лаконичность: Не нужно описывать каждое поле вручную.
  • Безопасность: TypeScript проверит, что вы используете только разрешённые ключи.

Record vs простой объект: наглядный пример

Допустим, у нас есть статусы заказа: pending, shipped, delivered. Без Record пришлось бы писать интерфейс с тремя полями:

interface OrderStatusMap {
  pending: string;
  shipped: string;
  delivered: string;
}

const statusDescriptions: OrderStatusMap = {
  pending: 'Ожидает обработки',
  shipped: 'Отправлен',
  delivered: 'Доставлен'
};

А с Record — компактнее и нагляднее:

type OrderStatus = 'pending' | 'shipped' | 'delivered';

const statusDescriptions: Record<OrderStatus, string> = {
  pending: 'Ожидает обработки',
  shipped: 'Отправлен',
  delivered: 'Доставлен'
};

Плюс: если вы попытаетесь добавить cancelled в объект, TypeScript сразу укажет на ошибку — такого статуса нет в union-типе.

Топ-5 практических примеров использования Record

Давайте посмотрим, где Record реально упрощает жизнь.

1. Конфигурация приложения

Храните настройки с известными ключами, но разными типами значений:

type ConfigKeys = 'apiUrl' | 'timeout' | 'retryCount';
type ConfigValue = string | number | boolean;

const appConfig: Record<ConfigKeys, ConfigValue> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retryCount: 3
};

2. Маппинг enum или констант

Часто нужно сопоставить код и его человекочитаемое название:

enum HttpStatus {
  OK = 200,
  NotFound = 404,
  Error = 500
}

const statusMessages: Record<HttpStatus, string> = {
  [HttpStatus.OK]: 'Всё хорошо',
  [HttpStatus.NotFound]: 'Не найдено',
  [HttpStatus.Error]: 'Ошибка сервера'
};

3. Формы и валидация

Record отлично подходит для хранения ошибок валидации по полям:

type FormFields = 'email' | 'password' | 'name';
type ValidationErrors = Record<FormFields, string | null>;

const errors: ValidationErrors = {
  email: 'Некорректный email',
  password: null,
  name: 'Имя обязательно'
};

4. Кэш данных

Имитация кэша с ключами-идентификаторами:

type UserId = number;
type UserCache = Record<UserId, { name: string; age: number }>;

const cache: UserCache = {
  1: { name: 'Анна', age: 25 },
  2: { name: 'Иван', age: 30 }
};

5. Динамические маршруты в React-приложении

Record помогает типизировать пропсы:

type RouteParams = Record<string, string>;

function RouteComponent({ params }: { params: RouteParams }) {
  return <div>Параметр: {params.id}</div>;
}

Типизированные ключи: Record с union-типом

Самая мощная фича Record — использование union-типа в качестве ключей. Это даёт строгую проверку на этапе компиляции.

type Fruit = 'apple' | 'banana' | 'orange';
type FruitPrices = Record<Fruit, number>;

const prices: FruitPrices = {
  apple: 100,
  banana: 80,
  orange: 90
};

// Ошибка! Свойства "grape" нет в типе Fruit
// prices.grape = 120;

Если вам нужно сделать все поля необязательными — комбинируйте с Partial:

type PartialFruitPrices = Partial<Record<Fruit, number>>;
// { apple?: number; banana?: number; orange?: number; }

Record с вложенными объектами

Record может содержать другие объекты или даже другие Record:

type UserProfile = {
  name: string;
  age: number;
  email: string;
};

type UsersDatabase = Record<string, UserProfile>;

const users: UsersDatabase = {
  'user_1': { name: 'Алексей', age: 28, email: 'alex@example.com' },
  'user_2': { name: 'Мария', age: 32, email: 'maria@example.com' }
};

Частые ошибки новичков с Record в TypeScript

Вот три типичные проблемы, с которыми сталкиваются начинающие:

Ошибка 1: Несоответствие типов ключа и значения

// Ошибка: число нельзя использовать как ключ, если K — string
const bad: Record<string, number> = { 1: 'one' }; // ❌

Решение: Убедитесь, что тип ключа поддерживает значения, которые вы передаёте.

Ошибка 2: Забыли указать все ключи из union-типа

type Status = 'active' | 'inactive';
const map: Record<Status, string> = { active: 'Активен' }; // ❌ пропущен 'inactive'

Решение: Используйте Partial<Record<Status, string>&

Блоги

Рекомендаций по книгам