Пошук уроків, статей та іншого контенту
Опишіть об'єкти з довільною кількістю однотипних ключів через index signatures.
Індексна сигнатура описує об’єкт, у якому ключі не перелічені наперед, але всі значення мають один визначений тип.
Це корисно, коли об’єкт може містити довільну кількість властивостей:
const scores = {
Alice: 95,
Bob: 87,
Charlie: 91,
};Наперед невідомо, які саме імена користувачів з’являться в об’єкті, але зрозуміло, що кожне значення є числом.
Для такого об’єкта можна використати індексну сигнатуру:
type Scores = {
[username: string]: number;
};
const scores: Scores = {
Alice: 95,
Bob: 87,
Charlie: 91,
};Загальний синтаксис:
type TypeName = {
[key: string]: ValueType;
};key — ім’я параметра індексу. Воно має лише пояснювальне значення.
string — тип ключа.
ValueType — тип значень властивостей.
Назва параметра індексу може бути іншою:
type Scores = {
[name: string]: number;
};У цьому прикладі name і key працюють однаково.
Найпоширеніший варіант використовує ключі типу string:
interface Translations {
[language: string]: string;
}
const translations: Translations = {
hello: "Привіт",
goodbye: "До побачення",
thanks: "Дякую",
};TypeScript перевіряє, що всі властивості цього об’єкта мають значення типу string:
const translations: Translations = {
hello: "Привіт",
// goodbye: 42, // Помилка: number не можна призначити типу string
};До властивостей можна звертатися як через крапку, так і через квадратні дужки:
const translations: Translations = {
hello: "Привіт",
goodbye: "До побачення",
};
const first = translations.hello;
const second = translations["goodbye"];
console.log(first);
console.log(second);Квадратні дужки особливо корисні, коли ключ зберігається у змінній:
const key = "hello";
const translation = translations[key];
console.log(translation);Для індексних сигнатур використовують ключі типу string, number або symbol.
Найчастіше застосовують string:
type UserRoles = {
[username: string]: string;
};Індексна сигнатура з number описує доступ до властивостей за числовими індексами:
interface Scores {
[index: number]: number;
}
const scores: Scores = [95, 87, 91];
console.log(scores[0]);Масиви мають числові індекси, тому така сигнатура може описувати структуру масиву.
Однак числові індекси JavaScript під час доступу фактично пов’язані з рядковими властивостями. Якщо тип має і рядкову, і числову індексну сигнатуру, тип значення для числового індексу має бути сумісним із типом значення для рядкового:
interface Data {
[key: string]: string | number;
[index: number]: string;
}
const data: Data = {
name: "Alice",
0: "first",
};Тут числова сигнатура повертає лише string, а рядкова дозволяє string | number. string є підтипом string | number, тому структура коректна.
Об’єкт може мати відомі властивості та довільні властивості одночасно:
interface Product {
name: string;
price: number;
[property: string]: string | number;
}
const product: Product = {
name: "Keyboard",
price: 120,
category: "electronics",
color: "black",
};Типи явних властивостей мають бути сумісними з типом індексної сигнатури.
У попередньому прикладі name має тип string, а price — number. Обидва типи входять до string | number.
Якщо індексна сигнатура дозволяє лише string, властивість типу number спричинить помилку:
interface InvalidProduct {
[property: string]: string;
// price: number; // Помилка: number несумісний із string
}Щоб виправити тип, потрібно включити number до об’єднання:
interface Product {
[property: string]: string | number;
price: number;
}type та interfaceІндексні сигнатури можна оголошувати і в псевдонімах типів, і в інтерфейсах:
type EnvironmentVariables = {
[name: string]: string;
};
interface Configuration {
[name: string]: string;
}
const environment: EnvironmentVariables = {
NODE_ENV: "development",
API_URL: "https://example.test",
};
const configuration: Configuration = {
theme: "dark",
language: "uk",
};Вибір між type та interface залежить від стилю проєкту. Для опису словників часто використовують type, але технічно обидва варіанти підтримують індексні сигнатури.
Розглянемо об’єкт, у якому ключем є ідентифікатор товару, а значенням — його опис:
interface Product {
title: string;
price: number;
inStock: boolean;
}
interface ProductDictionary {
[productId: string]: Product;
}
const products: ProductDictionary = {
keyboard: {
title: "Механічна клавіатура",
price: 2500,
inStock: true,
},
mouse: {
title: "Бездротова миша",
price: 1200,
inStock: false,
},
};
function printProduct(
dictionary: ProductDictionary,
productId: string,
): void {
const product = dictionary[productId];
if (product === undefined) {
console.log("Товар не знайдено");
return;
}
console.log(`${product.title}: ${product.price} грн`);
}
printProduct(products, "keyboard");
printProduct(products, "monitor");Індексна сигнатура гарантує, що кожне значення в products має структуру Product.
Функція при цьому може прийняти будь-який рядковий ідентифікатор, оскільки перелік ключів не обмежений наперед.
readonly в індексних сигнатурахЯкщо властивості не повинні змінюватися після створення об’єкта, індексну сигнатуру можна позначити як readonly:
interface ReadonlyScores {
readonly [username: string]: number;
}
const scores: ReadonlyScores = {
Alice: 95,
Bob: 87,
};
console.log(scores.Alice);
// scores.Alice = 100;
// Помилка: властивість доступна лише для читанняreadonly забороняє змінювати значення через індекс:
const username = "Alice";
// scores[username] = 100;
// Помилка: індексний запис забороненийВодночас readonly не робить значення глибоко незмінними. Він захищає саме властивості словника.
Індексна сигнатура означає, що ключ може бути будь-яким рядком. Вона не обмежує об’єкт конкретним набором ключів:
type FeatureFlags = {
[feature: string]: boolean;
};
const flags: FeatureFlags = {
darkMode: true,
newDashboard: false,
experimentalSearch: true,
};Якщо набір ключів відомий наперед і має бути обмеженим, краще використати звичайні властивості або Record з об’єднанням літеральних типів:
type KnownFlags = {
darkMode: boolean;
notifications: boolean;
};Індексна сигнатура призначена саме для ситуацій, коли кількість або назви ключів можуть змінюватися.
Індексна сигнатура описує тип значення для кожного можливого ключа, але за замовчуванням не гарантує, що довільний ключ справді існує під час виконання:
interface Prices {
[productId: string]: number;
}
const prices: Prices = {
keyboard: 2500,
};
const mousePrice = prices["mouse"];У JavaScript звернення до відсутньої властивості повертає undefined. Тому під час роботи з динамічним ключем варто перевіряти результат:
const price = prices["mouse"];
if (price === undefined) {
console.log("Ціна не знайдена");
} else {
console.log(`Ціна: ${price} грн`);
}Це особливо важливо для словників, у яких ключ надходить від користувача або з іншого зовнішнього джерела.
Індексні сигнатури добре підходять для функцій, які читають або змінюють значення за динамічним ключем:
type Inventory = {
[productId: string]: number;
};
function addProduct(
inventory: Inventory,
productId: string,
quantity: number,
): void {
inventory[productId] = (inventory[productId] ?? 0) + quantity;
}
const inventory: Inventory = {};
addProduct(inventory, "keyboard", 5);
addProduct(inventory, "keyboard", 2);
addProduct(inventory, "mouse", 3);
console.log(inventory);У цьому прикладі:
productId може мати будь-яке рядкове значення;
кожне значення inventory є числом;
оператор ?? використовує 0, якщо товар ще відсутній у словнику.
type Counters = {
[name: string]: number;
};
const counters: Counters = {
requests: 10,
// errors: "five", // Помилка: значення має бути number
};Якщо словник має зберігати різні типи значень, потрібно явно описати об’єднання:
type Settings = {
[name: string]: string | boolean;
};
const settings: Settings = {
theme: "dark",
compactMode: true,
};Проте занадто широкі типи на кшталт any зменшують користь статичної перевірки:
type UnsafeDictionary = {
[key: string]: any;
};Краще вказувати конкретний тип або об’єднання допустимих типів.
interface UserData {
[key: string]: string;
// age: number; // Помилка
}Усі явні властивості мають відповідати індексній сигнатурі. У цьому випадку потрібно змінити сигнатуру:
interface UserData {
[key: string]: string | number;
age: number;
name: string;
}type Messages = {
[key: string]: string;
};
const messages: Messages = {
welcome: "Вітаємо!",
};
const message = messages["error"];Ключ "error" не оголошений у значенні messages, тому під час виконання результатом може бути undefined. Динамічний доступ потрібно перевіряти перед використанням.
type Options = {
[key: string]: boolean;
};Такий тип дозволяє не лише darkMode і debug, а будь-які рядкові ключі. Якщо потрібен обмежений набір, оголосіть властивості явно:
type Options = {
darkMode: boolean;
debug: boolean;
};Використовуйте індексну сигнатуру, коли:
кількість ключів невідома заздалегідь;
ключі мають один тип, наприклад string;
значення мають спільну структуру або спільний тип;
об’єкт використовується як словник.
Не використовуйте її лише для того, щоб уникнути оголошення відомих властивостей. Якщо властивості відомі, явний опис зазвичай точніший і краще захищає від помилок.
Індексна сигнатура описує об’єкти з довільною кількістю однотипних ключів.
Її синтаксис має вигляд [key: string]: ValueType.
Ключі можуть мати тип string, number або symbol.
Тип значення застосовується до всіх властивостей, включно з явно оголошеними.
Для незмінних властивостей використовуйте readonly.
Індексна сигнатура не гарантує, що довільний ключ існує в об’єкті під час виконання.
Якщо набір ключів відомий і обмежений, краще описати властивості явно.