Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Утилітарний тип record у TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Record<K, T>** - це utility-тип TypeScript, який створює об'єкт, де всі ключі з union K є обов'язковими і відповідають типу T. ```typescript type Status = "pending" | "approved" | "rejected"; const config: Record<Status, string> = { pending: "Очікує", approved: "Виконано", rejected: "Відхилено", }; ``` **Ключове:** На відміну від index-сигнатур, Record вимагає присутності всіх ключів з union.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Record<K, T>** - це utility-тип у TypeScript, який створює тип об'єкта, де кожен ключ з union K є обов'язковим і відповідає типу T. ## Теорія ### TL;DR - Аналогія: Record - це шаблон таблиці, де заголовки стовпців фіксовані, а кожна клітинка містить дані одного типу. - Головна різниця від `{ [key: string]: T }`: Record вимагає ВСІ ключі з union в об'єкті; index-сигнатури приймають будь-який рядок без перевірок. - Всередині `Record<K, T>` розгортається у `{ [P in K]: T }` - це mapped-тип. - Правило вибору: Record - якщо ключі відомі наперед. Index-сигнатура або `Map` - якщо ключі динамічні. ### Швидкий приклад ```typescript type Status = "pending" | "approved" | "rejected"; type StatusConfig = Record<Status, { label: string; color: string }>; const config: StatusConfig = { pending: { label: "Очікує", color: "#FFA500" }, approved: { label: "Виконано", color: "#00AA00" }, rejected: { label: "Відхилено", color: "#FF0000" } // Прибери будь-який ключ - TypeScript одразу покаже помилку. }; ``` Усі три ключі обов'язкові. В цьому весь контракт. ### Ключова різниця Record гарантує повноту покриття. Якщо оголосити `Record<"a" | "b" | "c", T>`, TypeScript вимагає всі три ключі під час компіляції. Index-сигнатура `{ [key: string]: T }` приймає будь-який рядок без перевірок. Record робить контракти явними. Index-сигнатури дають гнучкість. ### Коли використовувати - Конфіги з відомими ключами: ролі до дозволів, статус-коди до повідомлень, feature-флаги до boolean. - Lookup-таблиці: ID користувачів до профілів, коди валют до курсів. - Стейт-машини: кожен стан відображається на дозволені переходи або обробники. - Не використовуй Record, якщо ключі справді динамічні або необмежені. Там краще підходить index-сигнатура або `Map`. ### Як компілятор це обробляє TypeScript розгортає `Record<K, T>` у `{ [P in K]: T }`. Компілятор перебирає кожен елемент union K і створює обов'язкову властивість. Якщо якийсь ключ відсутній - помилка компіляції. В рантаймі накладних витрат немає. Record компілюється у звичайний JavaScript-об'єкт. ### Типові помилки **Помилка 1: Забутий ключ після розширення union** Найчастіше бачу це після рефакторингу: хтось додає нове значення до union і не оновлює об'єкт. ```typescript type Permission = "read" | "write" | "delete" | "admin"; type PermissionConfig = Record<Permission, boolean>; // Помилка TypeScript: властивість 'admin' відсутня const config: PermissionConfig = { read: true, write: true, delete: true, }; // Виправлення: додай усі ключі const configFixed: PermissionConfig = { read: true, write: true, delete: true, admin: false, }; ``` **Помилка 2: Record там, де потрібні опціональні ключі** ```typescript type Feature = "darkMode" | "analytics" | "beta"; // Record вимагає всі ключі, тому це не спрацює: const flags: Record<Feature, boolean> = { darkMode: true, analytics: true, // Помилка: відсутній "beta" }; // Виправлення: загорни у Partial const flagsFixed: Partial<Record<Feature, boolean>> = { darkMode: true, analytics: true, // Тепер валідно }; ``` **Помилка 3: `Record<string, T>` замість конкретного union** ```typescript // Це просто index-сигнатура під іншою назвою type D = Record<string, number>; // Те саме, що { [key: string]: number } // Якщо ключі відомі - перелічи їх явно type Currency = "USD" | "EUR" | "GBP"; const rates: Record<Currency, number> = { USD: 1.0, EUR: 0.92, GBP: 0.87, // TypeScript вимагає всі три }; ``` ### Де зустрічається в реальних проєктах - React: реєстри компонентів `Record<ComponentName, ComponentType>` - Redux: маппінг action-типів до обробників `Record<ActionType, ActionCreator>` - Express: HTTP-метод до обробника `Record<"GET" | "POST" | "DELETE", RequestHandler>` - Тести: фабрики моків `Record<UserRole, MockUser>` ### Питання на співбесіді **Q:** У чому різниця між `Record<K, T>` і `{ [P in K]: T }`? **A:** Вони еквівалентні. Record - це синтаксичний цукор над mapped-типом. Використовуй Record для простих контрактів ключ-значення. Mapped-тип пиши напряму, якщо потрібна умовна логіка для кожного ключа: `{ [P in K]: P extends "admin" ? AdminConfig : UserConfig }`. **Q:** Як зробити частину ключів Record опціональними? **A:** `Partial<Record<K, T>>` робить усі ключі опціональними. Якщо хочеш зберегти всі ключі обов'язковими, але дозволити `undefined`, використовуй `Record<K, T | undefined>`. **Q:** Коли обрати `Map<string, T>` замість `Record<string, T>`? **A:** Record підходить для статичних типізованих конфігурацій, які компілюються у звичайні об'єкти. Map - для динамічних даних з необмеженими ключами і вбудованими методами ітерації. Конфіги - Record. Кеші або дані від користувача - Map. **Q:** Що станеться, якщо додати зайвий ключ у рантаймі? **A:** JavaScript дозволяє. Record перевіряє контракт лише під час компіляції. TypeScript покаже помилку типу для `config.newKey = "value"`, але присвоєння виконається. ## Приклади ### Маппінг дозволів за ролями ```typescript type UserRoles = "admin" | "user" | "guest"; type RolePermissions = Record<UserRoles, string[]>; const rolePermissions: RolePermissions = { admin: ["create", "edit", "delete"], user: ["view", "edit"], guest: ["view"], }; // TypeScript знає: це string[], а не string[] | undefined console.log(rolePermissions["admin"]); // ["create", "edit", "delete"] ``` Кожен ключ з `UserRoles` обов'язковий. Додай `"moderator"` до union - TypeScript одразу вимагатиме властивість `moderator` в об'єкті. ### Обробники HTTP-методів в Express ```typescript import { Request, Response } from "express"; type RouteHandlers = Record< "GET" | "POST" | "DELETE", (req: Request, res: Response) => void >; const handlers: RouteHandlers = { GET: (req, res) => res.json({ data: [] }), POST: (req, res) => res.status(201).json({}), DELETE: (req, res) => res.status(204).send(), }; ``` Додай новий HTTP-метод до union - код не скомпілюється, поки не напишеш його обробник. Тип забезпечує повне покриття автоматично.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.