Индексные сигнатуры¶
К Object
в JavaScript (и, следовательно, в TypeScript) можно получить доступ с помощью строки, содержащей ссылку на любой другой JavaScript объект.
Вот краткий пример:
1 2 3 |
|
Мы храним строку "World"
под ключом "Hello"
. Помните, мы говорили, что он может хранить любой JavaScript объект, поэтому давайте сохраним экземпляр класса, чтобы показать концепцию:
1 2 3 4 5 6 7 8 9 10 |
|
Помните мы сказали, что к нему можно получить доступ с помощью строки. Если вы передадите в сигнатуру индекса любой другой объект, среда выполнения JavaScript вызовет для него .toString
перед получением результата. Это показано ниже:
1 2 3 4 5 6 7 8 9 10 11 |
|
Обратите внимание, что toString
будет вызываться всякий раз, когда obj
используется в позиции индекса.
Массивы немного отличаются. Для индексации числа
виртуальные машины JavaScript будут пытаться оптимизировать (в зависимости от того, действительно ли это массив, совпадают ли структуры хранимых элементов и т.д.). Таким образом, число
следует рассматривать как действительный метод доступа к объекту сам по себе (в отличие от строки
). Вот простой пример c массивом:
1 2 |
|
Итак, это JavaScript. Теперь давайте посмотрим на изящную обработку этой концепции в TypeScript.
Индексные сигнатуры в TypeScript¶
Во-первых, поскольку JavaScript неявно вызывает toString
для любой сигнатуры индекса в виде объекта, TypeScript выдаст вам ошибку, чтобы новички не стреляли себе в ногу (я вижу, как пользователи стреляют себе в ногу при постоянном использовании JavaScript на stackoverflow):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Причина для такого принуждения пользователя быть явным в том, что реализация toString
по умолчанию для объекта довольно ужасна, например в v8 он всегда возвращает [object Object]
:
1 2 3 4 5 6 7 8 |
|
Конечно, поддерживается число
, потому что
- Это необходимо для отличной поддержки массивов / кортежей.
- Даже если вы используете его для
obj
, его реализацияtoString
по умолчанию нормальна (не[object Object]
).
Пункт 2 показан ниже:
1 2 |
|
Итак, урок 1:
Сигнатуры индекса TypeScript должны быть либо строкой, либо числом.
Краткое примечание: символы
также действительны и поддерживаются TypeScript. Но пока не будем туда заходить. Будем двигаться маленькими шажками.
Объявление индексной сигнатуры¶
Итак, мы использовали any
, чтобы сказать TypeScript, что мы можем делать все, что захотим. Фактически мы можем явно указать сигнатуру для index. Например, скажем, вы хотите убедиться, что все, что хранится в объекте с использованием строки, соответствует структуре {message: string}
. Это можно сделать с помощью объявления { [index:string] : {message: string} }
. Это показано ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
СОВЕТ: имя сигнатуры индекса, например
index
в{ [index:string] : {message: string} }
не имеет значения для TypeScript и предназначен только для удобства чтения. Например если это имена пользователей, вы можете использовать{ [username:string] : {message: string} }
, чтобы помочь следующему разработчику, который просматривает код (который кстати может оказаться вами).
Конечно, числовые индексы также поддерживаются, например { [count: number] : SomeOtherTypeYouWantToStoreEgRebate }
Все члены должны соответствовать сигнатуре индекса string
¶
Как только у вас есть сигнатура индекса string
, все явные элементы также должны соответствовать этой сигнатуре индекса. Это показано ниже:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Это сделано для обеспечения безопасности, чтобы любой доступ к строке давал одинаковый результат:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Использование ограниченного набора строковых литералов¶
Сигнатура индекса может требовать, чтобы строки индекса были частями объединения литеральных строк, используя замапленные типы, например:
1 2 3 4 5 6 7 8 9 10 |
|
Это часто используется вместе с keyof typeof
для захвата типов словаря, описанных далее.
В общем случае определение словаря может быть отложено:
1 2 3 |
|
Используйте и строки и числа в качестве индексов¶
Это не стандартный вариант использования, но компилятор TypeScript, тем не менее, его поддерживает.
Однако у него есть ограничение: индексатор string
более строгий, чем индексатор number
. Это сделано намеренно, например чтобы разрешить следующий код:
1 2 3 4 5 6 7 8 9 |
|
Шаблон проектирования: вложенная индексная сигнатура¶
Рекомендации для API при добавлении индексных сигнатур
Довольно часто в сообществе JS можно встретить API, которые неправильно употребляют строковые индексаторы. Например стандартный шаблон для CSS в библиотеках JS:
1 2 3 4 5 6 7 8 9 10 11 |
|
Постарайтесь не смешивать таким образом индексаторы строк с валидными значениями. Например, опечатка останется невыявленной:
1 2 3 4 |
|
Вместо этого поместите вложение в отдельное свойство, например в имя nest
(или children
, или subnodes
и т.д.):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Исключение определенных свойств из сигнатуры индекса¶
Иногда вам нужно объединить свойства в сигнатуру индекса. Это не рекомендуется, и вам следует использовать упомянутый выше шаблон вложенной индексной сигнатуры.
Однако, если вы моделируете существующий JavaScript, вы можете обойти это с помощью типа пересечения. Ниже показан пример ошибки, с которой вы столкнетесь без использования пересечения:
1 2 3 4 5 6 7 8 |
|
Вот обходной путь с использованием типа пересечения:
1 2 3 4 5 6 7 |
|
Обратите внимание, что даже если вы можете объявить его для моделирования существующего JavaScript, вы не можете создать такой объект с помощью TypeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|