Перейти к основному содержимому

Индексные сигнатуры в TypeScript

Индексная сигнатура позволяет описать объект, имена свойств которого заранее неизвестны, и при этом указать, какой тип должно иметь каждое значение.


Как устроены индексные сигнатуры в TypeScript

Индексные сигнатуры дают возможность типизировать объекты с динамическими именами свойств, не теряя безопасности типов.

Они сообщают TypeScript, значение какого типа вернётся при чтении свойства через квадратные скобки (obj[key]), даже если точные имена свойств заранее неизвестны.

Главные идеи:

  • Динамический доступ к свойствам: работа с объектами, имеющими любые имена свойств.
  • Безопасность типов: каждое динамическое свойство должно хранить значение одного и того же вида.
  • Гибкие структуры данных: моделирование словарей, отображений и других таблиц поиска.
  • Ранние ошибки: ошибки в типах значений обнаруживаются на этапе компиляции.

Базовые индексные сигнатуры

Строковые индексные сигнатуры. Используйте индексную сигнатуру, когда имена свойств заранее неизвестны, но известна форма значений.

В синтаксисе применяются квадратные скобки: [key: type] описывает тип имён свойств (ключей), а после двоеточия указывается тип значений, которые эти свойства могут хранить.

Числовые индексные сигнатуры. TypeScript поддерживает не только строковые, но и числовые ключи.

Примечание: В JavaScript все ключи объекта хранятся как строки, даже числовые. TypeScript всё же различает эти два случая, чтобы помочь обнаружить логические ошибки при путанице между массивами и объектами.

Строковая индексная сигнатура

StringDictionary принимает любой строковый ключ, но каждое значение должно быть типа string. Чтение, добавление и обновление ключей работают, а присваивание числа является ошибкой.

Prompt
Покажите строковую индексную сигнатуру с помощью интерфейса StringDictionary, ключи которого являются строками, а значения — строками. Создайте объект names этого типа со значениями firstName: "Alice", lastName: "Smith" и ключом "100", сопоставленным со значением "Сто", выведите три значения с доступом через квадратные скобки, затем добавьте ключ age со строкой "30". Оставьте закомментированную строку, показывающую, что присваивание числа 30 было бы ошибкой типа.
Вывод: программа на TypeScript и захваченный вывод

Числовая индексная сигнатура

NumberDictionary использует числовые ключи. Поскольку типом значения является any, каждая запись может хранить значение своего вида.

Prompt
Покажите числовую индексную сигнатуру с помощью интерфейса NumberDictionary, ключи которого являются числами, а значения имеют тип any. Создайте объект scores со значениями 0: "Ноль", 1: 100 и 2: true, выведите три записи по числовому индексу, затем добавьте индекс 3, хранящий объект { passed: true }.
Вывод: программа на TypeScript и захваченный вывод

Продвинутые приёмы работы с индексными сигнатурами

Смешанные типы свойств. Индексную сигнатуру можно сочетать с явно названными свойствами. Названные свойства обязательны; всё остальное должно соответствовать индексной сигнатуре.

Важно: Когда вы смешиваете явные свойства с индексной сигнатурой, тип каждого явного свойства должен быть совместим с типом значения индексной сигнатуры.

Индексные сигнатуры только для чтения. Добавьте readonly к индексной сигнатуре, чтобы запретить изменение значений после создания объекта.

Об ограничении наборов ключей и преобразовании форм читайте в уроке о сопоставленных типах (Mapped Types).

Смешанные типы свойств

UserInfo требует наличия name и age и разрешает любое другое свойство при условии, что его значение имеет тип string или number.

Prompt
Объедините явные свойства с индексной сигнатурой в интерфейсе UserInfo: обязательные name: string и age: number плюс сигнатура [key: string]: string | number для любого другого свойства. Создайте корректный объект user со значениями name: "Alice", age: 30, address: "123 Main St" и zipCode: 12345. Добавьте закомментированный invalidUser, который показывает, что age: "thirty" и isAdmin: true каждый были бы ошибкой типа. Файл ничего не выводит.
Вывод: программа на TypeScript и захваченный вывод

Индексная сигнатура только для чтения

Читать names[0] можно, но запись по любому индексу типа ReadOnlyStringArray является ошибкой компиляции.

Prompt
Объявите интерфейс ReadOnlyStringArray с индексной сигнатурой readonly [index: number]: string. Присвойте массив ["Alice", "Bob", "Charlie"] переменной names этого типа, выведите names[0] и оставьте закомментированное присваивание в names[0] с пояснением, что запись через индексную сигнатуру только для чтения является ошибкой типа.
Вывод: программа на TypeScript и захваченный вывод

Примеры из реальной практики

Обработка ответов API. Индексные сигнатуры удобны для ответов API, где имена ресурсов и дополнительные поля метаданных не фиксированы.

Обработка ответа API

ApiResponse<T> позволяет полю data хранить любое имя ресурса, сопоставленное с массивом T, а полю meta — нести дополнительные поля помимо page и total.

Prompt
Смоделируйте обобщённый ответ API с помощью индексных сигнатур: интерфейс ApiResponse<T>, у которого поле data сопоставляет любое имя ресурса ([resourceType: string]) с T[], а поле meta содержит page: number, total: number плюс сигнатуру [key: string]: any для дополнительных метаданных. Объявите интерфейс User (id, name, email), создайте apiResponse: ApiResponse<User> с двумя пользователями (Alice и Bob) и метаданными page: 1, total: 2, timestamp: "2023-01-01T00:00:00Z", затем выведите имя первого пользователя.
Вывод: программа на TypeScript и захваченный вывод

Лучшие практики

Стоит делать:

  • Используйте индексные сигнатуры для коллекций с динамическими ключами.
  • Сочетайте их с явными свойствами для тех полей, которые вам известны.
  • Делайте типы значений конкретными — избегайте any.
  • Используйте readonly, когда значения не должны меняться.

Не стоит делать:

  • Браться за индексную сигнатуру, когда ключи известны, — лучше использовать фиксированный интерфейс.
  • Забывать, что каждое свойство, включая явные, должно соответствовать типу значения индексной сигнатуры.
  • Изобретать заново сопоставленные типы — используйте их для преобразования форм.

Частые ошибки

Распространённая ошибка — объявить явное свойство, тип которого не соответствует индексной сигнатуре. Чтобы это исправить, расширьте тип значения индексной сигнатуры.

Конфликт типов свойства и индекса

ConflictingTypes вызывает ошибку, потому что name: string не соответствует индексной сигнатуре с типом number; FixedTypes расширяет тип значения до number | string, поэтому оба свойства допустимы.

script.ts
  interface ConflictingTypes {
    [key: string]: number;
    name: string; // Error: not assignable to string index type 'number'
  }

  interface FixedTypes {
    [key: string]: number | string;
    name: string;  // OK
    age: number;   // OK
  }

Индексные сигнатуры и Record

Используйте индексную сигнатуру, когда ключи гибкие или динамические, а также когда нужно добавить другие свойства.

Используйте Record<K, T> для кратких и простых отображений одного типа ключа в один тип значения.

Индексная сигнатура и Record рядом

StringMap и StringRecord описывают одну и ту же форму — любой строковый ключ, сопоставленный со строковым значением, — записанную двумя разными способами.

script.ts
// Index signature
interface StringMap {
  [key: string]: string;
}

// Record
type StringRecord = Record<string, string>;

Заключение

Используйте индексные сигнатуры, когда ключи динамические, но форма значений постоянна.

По возможности сочетайте их с явными свойствами, а о более сложных преобразованиях читайте в уроках о сопоставленных типах (Mapped Types) и служебных типах (Utility Types).