Индексные сигнатуры¶
К 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 | |