Тип доступа по индексу находит тип свойства в другом типе с тем же синтаксисом квадратных скобок, который JavaScript использует для доступа к свойствам. Person["age"] это number, а Person["address"]["city"] это string.
Польза в едином источнике истины. Age это не вторая копия number: если Person["age"] станет string, каждый тип, записанный как Person["age"], изменится вместе с ним. Типы доступа по индексу существуют только при компиляции; они не порождают JavaScript.
Объединения ключей и T[keyof T]
Индекс может быть объединением ключей. Результатом будет объединение типов свойств:
Product[keyof Product] это способ сказать на уровне типов «любое значение свойства Product». Запрос несуществующего ключа это ошибка компиляции: Product["price"] даёт error TS2339: Property 'price' does not exist on type 'Product'.
Необязательные свойства включают undefined
С strictNullChecks (включён при strict) тип необязательного свойства включает undefined, как и его тип доступа по индексу:
type Profile = { name: string; nickname?: string };
type Nick = Profile["nickname"]; // string | undefined
Чтобы убрать undefined, оберните его: NonNullable<Profile["nickname"]> это string.
Типы элементов массива через T[number]
Массивы индексируются числами, поэтому индексация типа массива через number даёт тип элемента. Это работает и через несколько уровней:
Это полезно, когда тип приходит из места, которое вы не контролируете, например из сгенерированного клиента API: ApiResponse["data"]["users"][number] называет тип пользователя, хотя никто его не экспортировал.
Кортежи: индекс по позиции
Тип кортежа можно индексировать конкретной позицией, через number для объединения всех типов элементов и через "length" для его длины в виде литерального типа:
(typeof arr)[number]: объединение из const-массива
Самое частое применение на практике: хранить список допустимых значений в массиве (чтобы по нему можно было пройти во время выполнения) и выводить из него тип объединения (чтобы его проверял компилятор).
Работает это благодаря двум частям. as const делает массив readonly-кортежем литеральных типов; без этого ROLES имеет тип string[], а (typeof ROLES)[number] это просто string. Затем typeof превращает значение в тип, а [number] собирает его элементы в объединение. Скобки необязательны (typeof ROLES[number] означает то же самое), но делают порядок операций понятным.
Проверка строки по массиву
У этого readonly-кортежа есть один сюрприз. Его метод includes принимает только тип элемента, поэтому передать ему произвольную строку нельзя:
index.ts(6,25): error TS2345: Argument of type 'string' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
Для проверки расширьте массив до readonly string[] и превратите функцию в защитник типа, чтобы успешная проверка сужала значение:
Этот приём даёт один список для обеих задач: проверка во время выполнения использует массив, а тип выводится из него, поэтому они не могут разойтись.
Доступ по индексу в дженериках: T[K]
Внутри дженерика T[K] с K extends keyof T это тип того свойства, которое выбрал вызывающий код:
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 28 },
];
const names = pluck(users, "name"); // string[]
const ages = pluck(users, "age"); // number[]
Именно ограничение K extends keyof T делает T[K] допустимым. Без него TypeScript не может знать, что K это ключ T, и сообщает error TS2536: Type 'K' cannot be used to index type 'T'.
Синтаксис коротко
| Запись | Значение | Пример результата |
|---|---|---|
T["key"] | тип одного свойства | Person["age"] это number |
T["a" | "b"] | объединение типов этих свойств | string | number |
T[keyof T] | объединение типов всех свойств | тип любого значения |
T["a"]["b"] | вложенное свойство | Person["address"]["city"] |
Arr[number] | тип элемента массива | User[] даёт User |
Tup[0] | элемент кортежа на позиции | тип первого элемента |
Tup["length"] | длина кортежа как литерал | 3 |
(typeof arr)[number] | объединение значений const-массива | "admin" | "editor" |
T[typeof key] | индекс типом константы | то же, что T["name"] |
Индекс всегда является типом. Person[key], где key это переменная const, завершается двумя ошибками: error TS2538: Type 'key' cannot be used as an index type. и error TS2749: 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?.
Часто задаваемые вопросы
Что такое тип доступа по индексу в TypeScript?
Тип вида T[K], который находит тип свойства K в типе T. С type Person = { name: string; age: number } Person["age"] это number. Он использует тот же синтаксис квадратных скобок, что и доступ к свойствам в JavaScript, но работает с типами при компиляции.
Как получить тип элемента массива в TypeScript?
Проиндексируйте тип массива через number: для type Users = User[] Users[number] это User. Для значения-массива сочетайте это с typeof: (typeof users)[number].
Как превратить массив строк в тип объединения?
Объявите массив с as const, чтобы TypeScript сохранил литеральные значения, затем проиндексируйте его через number: const roles = ["admin", "editor"] as const; type Role = (typeof roles)[number]; даёт "admin" | "editor". Без as const тип элемента будет просто string.
Почему нельзя использовать переменную как индекс в типе доступа по индексу?
Индекс должен быть типом. Person[key] при const key = "name" не работает (ошибки TS2538 и TS2749), потому что key это значение. Напишите Person[typeof key] или используйте литерал напрямую: Person["name"].
Что означает T[keyof T]?
Это индексация T объединением всех его ключей, поэтому результат это объединение всех типов его свойств. Для { a: string; b: number } это string | number. Так на уровне типов говорят «любое значение этого объекта».