Учитывая интерфейс, мы можем получить доступ к его свойствам определенного типа ключа, например. string
:
interface AB {
a: number;
b: boolean;
}
type PropertiesByString = AB[keyof AB & string]
Это работает, потому что keyof AB & string
— это "a" | "b"
, и все эти ключи являются действительными ключами AB
. Та же логика неприменима к массивам:
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
type Person = typeof MyArray[number];
Код действителен, но number
здесь включает в себя 4
и многое другое. Почему number
здесь разрешено?
Использование number
— это особый способ индексации типов массивов.
Тип массива TypeScript (по сравнению с кортежем ) не имеет фиксированной длины. typeof MyArray
это просто { name: string; age: number; }[]
. Массив переменных времени выполнения может содержать 3 элемента, но на уровне типа об этом нет никакой информации. Теоретически массив может иметь любую длину. Следовательно, индексировать с помощью number
часто бывает проще, чем использовать случайные индексы, которые индексируют один и тот же тип.
typeof MyArray[4]
, typeof MyArray[3]
и typeof MyArray[23802302]
все действительны. Попробуйте!
Я поддерживаю вашу доброту и то, что я не знал, что typeof MyArray[23802302]
не является ошибкой. Но строка «number
— это особый способ» и ссылка не могут разрешить мое замешательство по поводу того, «почему это действительно».
Когда ты пишешь
const myArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
TypeScript предполагает, что это тип
// const myArray: { name: string; age: number; }[]
это просто сокращенный способ записи
// const myArray: Array<{ name: string; age: number; }>
Итак, myArray
имеет тип массива . Если вы посмотрите на определение типа TypeScript для Array<T> , оно имеет числовую сигнатуру индекса:
interface Array<T> {
// ⋮
[n: number]: T;
}
Это означает, что TypeScript позволит вам индексировать его с помощью числового ключа. В частности, индексированный доступ Array<T>[number]
просто вернет вам T
.
Кажется, вы спрашиваете, почему он не жалуется, потому что содержимое массива не имеет значений для каждого числового индекса. Ответ в том, что тип не отслеживает содержимое.
Более того, подписи индекса в форме {[key: K]: V}
не означают, что «каждый ключ типа K
существует в этом объекте и имеет значение типа V
». Вместо этого это означает «если в этом объекте существует ключ типа K
, то его значение имеет тип V
». Таким образом, (typeof myArray)[number]
всегда есть { name: string; age: number; }
, хотя вы можете ожидать увидеть undefined
на практике, если выполните произвольную операцию индексации. Вы можете включить --noUncheckedIndexedAccess, если хотите видеть undefined
как возможное значение для операции индексирования:
const person = myArray[4];
// if --noUncheckedIndexedAccess is off
// const person: { name: string; age: number; }
// if --noUncheckedIndexedAccess is on
// const person: { name: string; age: number; } | undefined
но тип индексированного доступа останется прежним.
Существуют типы кортежей , которые ограничивают массивы содержанием определенных типов значений по определенным индексам и длина которых известна заранее. Если вы используете утверждение const в литерале инициализирующего массива, вы получите что-то более близкое к тому типу, который вы предположительно ищете:
const myArrayConst = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
] as const;
/* const myArrayConst: readonly [{
readonly name: "Alice";
readonly age: 15;
}, {
readonly name: "Bob";
readonly age: 23;
}, {
readonly name: "Eve";
readonly age: 38;
}] */
Теперь TypeScript знает, что length
— это 3
, и будет жаловаться, если увидит, что вы индексируете число, заведомо выходящее за пределы:
let personConst: Person;
personConst = myArrayConst[0]; // okay
personConst = myArrayConst[4]; // error! no element at index '4'
// error! Type 'undefined' is not assignable
Но опять же, это не меняет того, что происходит при цифровой подписи индекса:
type PersonConst = (typeof myArrayConst)[number];
/* type PersonConst = {
readonly name: "Alice";
readonly age: 15;
} | {
readonly name: "Bob";
readonly age: 23;
} | {
readonly name: "Eve";
readonly age: 38;
} */
myArrayConst[Math.random()] // okay, number is still a valid index
Индексирование по-прежнему разрешено, и индексированный доступ по-прежнему дает вам тип элемента без undefined
.
Тип
MyArray
— этоArray<...>
, аArray
имеет индексную сигнатуруnumber
в определении типа. Выведенный типAB
не имеет индексной сигнатуры,number
или чего-то еще, поэтому он не работает. TypeScript больше заботится о типах, чем о фактических значениях. Всегда считается приемлемым индексировать массив с произвольнымnumber
, независимо от того, какие данные в нем могут присутствовать. Это полностью решает вопрос? Или я что-то упускаю?