#typescript
#typescript
Вопрос:
Я читал индексируемые типы, и шаблон «словарь» меня смутил:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
interface StringArray {
[index: number]: string;
}
В чем разница между ними?
Как вы вообще инициализируете переменную с типом NumberOrStringDictionary
? И почему NumberOrStringDictionary
поля ( length
и name
) зависят от поля, подписанного индексом?
Чем NumberOrStringDictionary
отличается от этого:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
Ответ №1:
Этот тип:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number;
name: string;
}
подразумевает, что значение типа NumberOrStringDictionary
должно содержать свойство с именем length
типа number
свойство с именем name
типа string
. Кроме того, значение этого типа может содержать свойства с любым string
именем, если значения таких свойств можно присвоить string | number
.
Несколько моментов для уточнения:
- Подписи индекса не могут конфликтовать с любыми другими ключами. Например,
length
свойство имеет типnumber
, поэтому оно совместимо с сигнатурой индекса (number
может быть присвоеноnumber | string
). И этоname
свойство также совместимо. Если вы попытаетесь добавить свойство какого-либо несовместимого типа, вы получите сообщение об ошибке:interface Oops extends NumberOrStringDictionary { acceptable: boolean; // error! //~~~~~~~~ <-- boolean not assignable to string | number }
Если у меня есть ключ типа string и я считываю свойство по этому ключу из значения type
NumberOrStringDictionary
, каков тип этого свойства? Это такnumber | string
. Теперь представьте, что произошло бы, если бы вам разрешили добавить несовместимое свойство (напримерacceptable: boolean
). В этом случае это больше не будет правдой. Если ключ, который я выбираю, оказывается, есть"acceptable"
, тоboolean
выходит a, которого нетnumber | string
, значит, что-то не так. Чтобы быть в безопасности, вы должны ожидатьnumber | string | boolean
.Вы можете думать о свойствах неиндексной подписи как о частных случаях индексной подписи, когда вы знаете, что ключ свойства определенно существует и что значение имеет какой-то, возможно, более конкретный тип. Известные свойства следует рассматривать как частные / особые случаи индексатора. «Все свойства этого объекта имеют тип
string | number
. В частности,length
свойство этого объекта имеет типnumber
, аname
свойство этого объекта имеет типstring
«. Это все равно, что сказать: «Все мои домашние животные — собаки. Фидо здесь — пудель. » Несовместимость будет выглядеть так: «Все мои домашние животные — собаки. Фидо здесь — попугай «. Вы можете так сказать, но это не согласуется. Возможно, вы думаете об индексных сигнатурах как о случае «по умолчанию» или «все остальное» или «остальное» («Мои домашние животные — все собаки, кроме этого попугая»), но это не то, что это означает в TypeScript. Существует открытая проблема, microsoft / TypeScript # 17867 запрашивает такую конструкцию в языке, но ее еще нет (есть обходные пути). - Подпись индекса обычно не означает, что будут отображаться свойства со всеми
string
ключами. Вам разрешено указывать столько или столько таких свойств, сколько вы хотите. Поэтому возможно, что при проверке свойства typeNumberOrStringDictionary
оно будет отсутствовать. Когда вы прочитаете такое отсутствующее свойство, вы получите значение типаundefined
, а не ofstring | number
, но компилятор сделает вид, что это такstring | number
. В TypeScript 4.1 введен флаг компилятора для обработкиundefined
как возможного результата чтения любого свойства подписи индекса, но по умолчанию он не включен.
Возможно, было бы полезно просмотреть некоторые примеры и посмотреть, что принимается, а что отклоняется компилятором. Вот допустимое назначение:
const valid: NumberOrStringDictionary = {
length: 1,
name: "bob",
someOtherKey: 123,
someOtherKey2: "hey"
};
Реквизит length
и name
свойства находятся там, а дополнительные свойства соответствуют подписи индекса. Это также допустимо:
const alsoValid: NumberOrStringDictionary = {
length: 1,
name: "bob",
};
потому что вам не нужно добавлять какие-либо такие свойства индексной подписи. Теперь об ошибках:
const invalid1: NumberOrStringDictionary = { // error!
//~~~~~~~~ <- property "name" is missing
length: 1,
someOtherKey: 123,
someOtherKey2: "key"
};
Вы не можете пропустить обязательное свойство. Также:
const invalid2: NumberOrStringDictionary = {
length: "fred", // error!
//~~~~ <-- string is not assignable to number
name: "bob",
someOtherKey: 123,
someOtherKey2: "key"
};
Вы не можете указать неправильный тип требуемого свойства. И, наконец,:
const invalid3: NumberOrStringDictionary = {
length: 1,
name: "bob",
someOtherKey: 123,
someOtherKey2: true // error!
//~~~~~~~~~~~ <-- boolean is not string | number
};
Вы не можете добавить свойство, которое конфликтует с сигнатурой индекса.
Комментарии:
1. Я понимаю все, кроме того, почему
[index: string]
тип s должен быть совместим с типами других полей? Это просто так, как это реализовано в typescript, или я что-то здесь упускаю? Не могли бы вы, пожалуйста, прояснить это?ts interface NumberDictionary { [index: string]: number; length: number; // ok, length is a number name: string; // error, the type of 'name' is not a subtype of the indexer }
Почему[index: string]
свойство должно заботиться о типеname
свойства?2. Если у меня есть ключ типа
string
, и я считываю свойство по этому ключу из значения типа{[index: string]: number; length: number}
, каков тип этого свойства? Это такnumber
. Теперь представьте, что произошло бы, если бы вам разрешили добавить несовместимое свойство (напримерname: string
). В этом случае это больше не будет правдой. Если я выбираю ключ"name"
, тоstring
выходит a, которого нетnumber
, значит, что-то не так. TypeScript обеспечивает согласованность здесь. Имеет ли это смысл?3. Известные свойства следует рассматривать как частные / особые случаи индексатора. «Все свойства этого объекта имеют тип
string | number
. В частности ,length
свойство этого объекта имеет типnumber
, аname
свойство этого объекта имеет типstring
«. Это все равно, что сказать: «Все мои домашние животные — собаки. Фидо здесь — пудель. » Несовместимость будет выглядеть так: «Все мои домашние животные — собаки. Фидо здесь — попугай «. Вы можете так сказать , но это не согласуется.4. Я думаю, вы можете думать об индексной подписи как о случае «по умолчанию» или «все остальное» или «остальное» («Мои домашние животные — все собаки, кроме этого попугая»), но это не то, что это означает в TypeScript. Существует открытая проблема, microsoft / TypeScript # 17867 запрашивает такую конструкцию в языке, но ее еще нет (есть обходные пути).
5.
The known properties should be thought of as particular/special cases of the indexer.
У меня был момент — Ага, когда я читал это. Спасибо.
Ответ №2:
Объекты в JS можно рассматривать как словарь. Они могут принимать произвольные ключи, которые сопоставляются с соответствующими значениями. Эта двойственность объекта и словаря также видна в синтаксисе:
const o = {key: 1};
console.log(o['key']);
console.log(o.key)
Свойства можно добавлять и удалять во время выполнения.
В общем, большинство объектов имеют постоянный, известный набор свойств, и вы моделируете их как обычные интерфейсы в TypeScript.
Вам нужно использовать индексированный тип, если вы хотите моделировать объекты, которые следуют правилам JS
- может содержать любое свойство
- может быть изменен во время выполнения путем добавления и удаления свойств
Обратите внимание, что существует 2 вида индексов
- число — вы можете индексировать только через число (что означает, что вы используете их как массив)
- строка — вы можете индексировать с помощью любой строки (вы используете их как dict)
В вашем примере:
interface StringArray {
[index: number]: string; // number index
}
interface NumberOrStringDictionary {
[index: string]: number | string; // string index
length: number;
name: string;
}
Переменные этих интерфейсов определяются следующим образом:
const myArray: StringArray = ["Bob", "Fred"];
console.log(myArray[0])
const d: NumberOrStringDictionary = {
a: 1,
b: 2
length: 2,
name: 'myDict'
}
// You can add and remove properties in runtime
d.c = 1;
console.log(d['c'])
delete d.b;
console.log(d['c'])
Индексированные типы позволяют указывать типы значений свойств.
const d1: NumberOrStringDictionary = {
a: true, // error: Type 'boolean' is not assignable to type 'string | number'.
b: {}, // error: Type '{}' is not assignable to type 'string | number'.
length: 1,
name: 'myDict'
}
Как описано в ссылке из исходного сообщения, если вы добавляете дополнительные свойства к типу, индексируемому строками, типы этих свойств должны быть совместимы с типом, указанным для индекса.