Вы правы, что запутались. Подписи индекса означают несколько вещей, и они означают немного разные вещи в зависимости от того, где и как вы спрашиваете.
Во-первых, подписи индекса подразумевают, что все объявленные свойства в типе должны иметь совместимый тип .
interface NotLegal {
// Error, 'string' isn't assignable to 'number'
x: string;
[key: string]: number;
}
Это определяющий аспект сигнатур индекса - они описывают объект с различными ключами свойств, но согласованным типом для всех этих ключей. Это правило предотвращает появление неправильного типа при обращении к свойству через косвенное обращение:
function fn(obj: NotLegal) {
// 'n' would have a 'string' value
const n: number = obj[String.fromCharCode(120)];
}
Во-вторых, подписи индекса позволяют выполнять запись в любой индекс с совместимым типом .
interface NameMap {
[name: string]: number;
}
function setAge(ageLookup: NameMap, name: string, age: number) {
ageLookup[name] = age;
}
Это вариант использования ключа для подписей индекса: у вас есть некоторый набор ключей, и вы хотите сохранить значение, связанное с ключом.
В-третьих, подписи индекса подразумевают существование любого свойства, которое вы конкретно запрашиваете:
interface NameMap {
[name: string]: number;
}
function getMyAge(ageLookup: NameMap) {
// Inferred return type is 'number'
return ageLookup["RyanC"];
}
Поскольку x["p"]
и x.p
ведут себя одинаково в JavaScript, TypeScript обрабатывает их эквивалентно:
// Equivalent
function getMyAge(ageLookup: NameMap) {
return ageLookup.RyanC;
}
Это согласуется с тем, как TypeScript просматривает массивы, то есть предполагается, что доступ к массиву является внутренним. Он также эргономичен для подписей индекса, потому что, как правило, у вас есть известный набор ключей, и вам не нужно выполнять дополнительную проверку:
interface NameMap {
[name: string]: number;
}
function getAges(ageLookup: NameMap) {
const ages = [];
for (const k of Object.keys(ageLookup)) {
ages.push(ageLookup[k]);
}
return ages;
}
Однако подписи индекса не подразумевает, что любое произвольное, неопределенное свойство будет присутствовать, когда речь заходит о связи типа с сигнатурой индекса с типом с объявленными свойствами:
interface Point {
x: number;
y: number;
}
interface NameMap {
[name: string]: number;
}
const m: NameMap = {};
// Not OK, which is good, because p.x is undefined
const p: Point = m;
Этот вид присваиванияочень маловероятно, что это будет правильным на практике!
Это отличительная черта между { [k: string]: any }
и any
- вы можете читать и записывать свойства любого типа на объекте с сигнатурой индекса, но это может 'не может использоваться вместо любого типа, например any
.
Каждое из этих поведений индивидуально очень оправдано, но в целом наблюдаются некоторые несоответствия.
Например,эти две функции идентичны с точки зрения их поведения во время выполнения, но TypeScript считает, что только одна из них была вызвана неправильно:
interface Point {
x: number;
y: number;
}
interface NameMap {
[name: string]: number;
}
function A(x: NameMap) {
console.log(x.y);
}
function B(x: Point) {
console.log(x.y);
}
const m: NameMap = { };
A(m); // OK
B(m); // Error
В целом, когда вы пишете сигнатуру индекса для типа, вы говорите:
- Это нормально для чтения / записи этого объекта с произвольным ключом
- Когда я читаю определенное свойство из этого объекта через произвольный ключ, оно всегда присутствует
- Этот объект не имеет буквально каждого имени свойства в целях совместимости типов