Typescript property by index

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

К Object в JavaScript (и, следовательно, в TypeScript) можно получить доступ с помощью строки, содержащей ссылку на любой другой JavaScript объект.

let foo: any = <>; foo['Hello'] = 'World'; console.log(foo['Hello']); // World 

Мы храним строку «World» под ключом «Hello» . Помните, мы говорили, что он может хранить любой JavaScript объект, поэтому давайте сохраним экземпляр класса, чтобы показать концепцию:

class Foo  constructor(public message: string) <> log()  console.log(this.message); > > let foo: any = <>; foo['Hello'] = new Foo('World'); foo['Hello'].log(); // World 

Помните мы сказали, что к нему можно получить доступ с помощью строки. Если вы передадите в сигнатуру индекса любой другой объект, среда выполнения JavaScript вызовет для него .toString перед получением результата. Это показано ниже:

let obj =  toString()  console.log('toString вызывается'); return 'Hello'; >, >; let foo: any = <>; foo[obj] = 'World'; // toString вызывается console.log(foo[obj]); // toString вызывается, World console.log(foo['Hello']); // World 

Обратите внимание, что toString будет вызываться всякий раз, когда obj используется в позиции индекса.

Массивы немного отличаются. Для индексации числа виртуальные машины JavaScript будут пытаться оптимизировать (в зависимости от того, действительно ли это массив, совпадают ли структуры хранимых элементов и т.д.). Таким образом, число следует рассматривать как действительный метод доступа к объекту сам по себе (в отличие от строки ). Вот простой пример c массивом:

let foo = ['World']; console.log(foo[0]); // World 

Итак, это JavaScript. Теперь давайте посмотрим на изящную обработку этой концепции в TypeScript.

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

Во-первых, поскольку JavaScript неявно вызывает toString для любой сигнатуры индекса в виде объекта, TypeScript выдаст вам ошибку, чтобы новички не стреляли себе в ногу (я вижу, как пользователи стреляют себе в ногу при постоянном использовании JavaScript на stackoverflow):

 1 2 3 4 5 6 7 8 9 10 11 12 13
let obj =  toString()  return 'Hello'; >, >; let foo: any = <>; // ОШИБКА: сигнатура индекса должна быть строкой, числом . foo[obj] = 'World'; // ИСПРАВЛЕНИЕ: TypeScript заставляет вас быть явным foo[obj.toString()] = 'World'; 

Причина для такого принуждения пользователя быть явным в том, что реализация toString по умолчанию для объекта довольно ужасна, например в v8 он всегда возвращает [object Object] :

let obj =  message: 'Hello' >; let foo: any = <>; // ОШИБКА: сигнатура индекса должна быть строкой, числом . foo[obj] = 'World'; // Вот где вы на самом деле хранили! console.log(foo['[object Object]']); // World 

Конечно, поддерживается число , потому что

  1. Это необходимо для отличной поддержки массивов / кортежей.
  2. Даже если вы используете его для obj , его реализация toString по умолчанию нормальна (не [object Object] ).
console.log((1).toString()); // 1 console.log((2).toString()); // 2 

Сигнатуры индекса TypeScript должны быть либо строкой, либо числом.

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

Объявление индексной сигнатуры¶

Итак, мы использовали any , чтобы сказать TypeScript, что мы можем делать все, что захотим. Фактически мы можем явно указать сигнатуру для index. Например, скажем, вы хотите убедиться, что все, что хранится в объекте с использованием строки, соответствует структуре . Это можно сделать с помощью объявления < [index:string] : > . Это показано ниже:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
let foo:  [index: string]:  message: string > > = <>; /** * Необходимо хранить в соответствии структуре */ /** Ok */ foo['a'] =  message: 'some message' >; /** Ошибка: должно содержать `message` типа string. У вас опечатка в * `message` */ foo['a'] =  messages: 'some message' >; /** * Тип также проверяется на чтение */ /** Ok */ foo['a'].message; /** Ошибка: messages не существует. У вас опечатка в `message` */ foo['a'].messages; 

СОВЕТ: имя сигнатуры индекса, например index в < [index:string] : > не имеет значения для TypeScript и предназначен только для удобства чтения. Например если это имена пользователей, вы можете использовать < [username:string] : > , чтобы помочь следующему разработчику, который просматривает код (который кстати может оказаться вами).

Конечно, числовые индексы также поддерживаются, например

Все члены должны соответствовать сигнатуре индекса string ¶

Как только у вас есть сигнатура индекса string , все явные элементы также должны соответствовать этой сигнатуре индекса. Это показано ниже:

 1 2 3 4 5 6 7 8 9 10 11 12
/** Okay */ interface Foo  [key: string]: number; x: number; y: number; > /** Ошибка */ interface Bar  [key: string]: number; x: number; y: string; // ОШИБКА: Свойство `y` должно иметь тип number > 

Это сделано для обеспечения безопасности, чтобы любой доступ к строке давал одинаковый результат:

 1 2 3 4 5 6 7 8 9 10 11 12
interface Foo  [key: string]: number; x: number; > let foo: Foo =  x: 1, y: 2 >; // Напрямую foo['x']; // число // Опосредственно let x = 'x'; foo[x]; // число 

Использование ограниченного набора строковых литералов¶

Сигнатура индекса может требовать, чтобы строки индекса были частями объединения литеральных строк, используя замапленные типы, например:

type Index = 'a' | 'b' | 'c'; type FromIndex =  [k in Index]?: number >; const good: FromIndex =  b: 1, c: 2 >; // Ошибка: // Тип '< b: number; c: number; d: number; >' нельзя присвоить типу 'FromIndex'. // Литерал объекта может указывать только известные свойства, а 'd' // не существует в типе 'FromIndex'. const bad: FromIndex =  b: 1, c: 2, d: 3 >; 

Это часто используется вместе с keyof typeof для захвата типов словаря, описанных далее.

В общем случае определение словаря может быть отложено:

type FromSomeIndexK extends string> =  [key in K]: number; >; 

Используйте и строки и числа в качестве индексов¶

Это не стандартный вариант использования, но компилятор TypeScript, тем не менее, его поддерживает.

Однако у него есть ограничение: индексатор string более строгий, чем индексатор number . Это сделано намеренно, например чтобы разрешить следующий код:

interface ArrStr  [key: string]: string | number; // Должен согласовываться со всеми // элементами [index: number]: string; // Может быть подмножеством индексатора строк // Просто пример элемента length: number; > 

Шаблон проектирования: вложенная индексная сигнатура¶

Рекомендации для API при добавлении индексных сигнатур

Довольно часто в сообществе JS можно встретить API, которые неправильно употребляют строковые индексаторы. Например стандартный шаблон для CSS в библиотеках JS:

interface NestedCSS  color?: string; [selector: string]: string | NestedCSS | undefined; > const example: NestedCSS =  color: 'red', '.subclass':  color: 'blue', >, >; 

Постарайтесь не смешивать таким образом индексаторы строк с валидными значениями. Например, опечатка останется невыявленной:

const failsSilently: NestedCSS =  colour: 'red', // Ошибок нет, так как colour является допустимым // селектором строки >; 

Вместо этого поместите вложение в отдельное свойство, например в имя nest (или children , или subnodes и т.д.):

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
interface NestedCSS  color?: string; nest?:  [selector: string]: NestedCSS; >; > const example: NestedCSS =  color: 'red', nest:  '.subclass':  color: 'blue', >, >, >; const failsSilently: NestedCSS =  colour: 'red', // Ошибка TS: неизвестное свойство `colour` >; 

Исключение определенных свойств из сигнатуры индекса¶

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

Однако, если вы моделируете существующий JavaScript, вы можете обойти это с помощью типа пересечения. Ниже показан пример ошибки, с которой вы столкнетесь без использования пересечения:

type FieldState =  value: string; >; type FormState =  isValid: boolean; // Ошибка: не соответствует сигнатуре индекса [fieldName: string]: FieldState; >; 

Вот обходной путь с использованием типа пересечения:

type FieldState =  value: string; >; type FormState =  isValid: boolean > &  [fieldName: string]: FieldState; >; 

Обратите внимание, что даже если вы можете объявить его для моделирования существующего JavaScript, вы не можете создать такой объект с помощью TypeScript:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
type FieldState =  value: string; >; type FormState =  isValid: boolean > &  [fieldName: string]: FieldState; >; // Используйте это для какого-нибудь объекта JavaScript, который вы откуда-то // получаете declare const foo: FormState; const isValidBool = foo.isValid; const somethingFieldState = foo['something']; // Использование его для создания объекта TypeScript не сработает const bar: FormState =  // Ошибка: `isValid` не может быть присвоен `FieldState` isValid: false, >; 

Источник

Читайте также:  Свободная лицензия python software foundation license
Оцените статью