Усі три описують «відповідність ключів значенням», але з різною точністю.
Індексна сигнатура - об'єкт з довільними ключами певного типу:
interface Prices {
[sku: string]: number;
}
Record<K, V> - те саме коротше, але з важливою можливістю: ключі можуть бути обмеженим набором:
type Prices = Record<string, number>; // як індексна сигнатура
type Labels = Record<'draft' | 'published' | 'archived', string>;
const labels: Labels = {
draft: 'Чернетка',
published: 'Опубліковано',
archived: 'В архіві', // пропустити ключ - помилка
};
З обмеженим набором ключів TypeScript вимагає всі ключі - додали новий статус у тип, і компілятор покаже кожен словник, де бракує перекладу. Це дуже корисно для мап статусів, перекладів, конфігурацій.
Map<K, V> - окрема структура даних під час виконання, а не тип об'єкта:
const cache = new Map<number, User>();
cache.set(user.id, user);
const cached = cache.get(5); // User | undefined
Коли що:
Звичайний об'єкт (Record) |
Map |
|
|---|---|---|
| ключі | рядки (і символи) | будь-які: числа, об'єкти |
| порядок | цілочисельні ключі сортуються | порядок додавання |
| JSON | серіалізується | {} - треба перетворювати |
| часте додавання й видалення | повільніше | оптимізовано |
| розмір | Object.keys(o).length |
map.size |
Пастки:
Record<string, V>бреше про наявність ключа:prices['unknown']має типnumber, хоча під час виконання -undefined. РятуєnoUncheckedIndexedAccess(тип стаєnumber | undefined) - уMap.get()це вбудовано;- ключі з даних користувача в звичайному об'єкті можуть зіткнутися з
__proto__чиconstructor. Для довільних ключів -MapабоObject.create(null); - числові ключі в об'єкті стають рядками:
{ 1: 'a' }має ключ'1'.
Правило: фіксований набір ключів - Record з об'єднанням; довільні ключі з даних, що часто змінюються, - Map; дані для JSON (відповіді API, конфіги) - об'єкти.