История вопроса
TypeScript с самого начала разрабатывался для строгой типизации объектов и написания более предсказуемого кода на JavaScript. Одним из инструментов для обеспечения безопасности при доступе к свойствам объектов стал оператор keyof, который появился в TypeScript 2.1. Он позволяет создавать типы, представляющие набор строковых (и символьных) ключей доступных в заявленном типе объекта.
Проблема
В чистом JavaScript свойства объекта могут запрашиваться по любой строке, и если ошибиться — получится undefined или ошибка, которая будет найдена только во время выполнения. Без строгой типизации легко сделать опечатку или забыть об обновлении строки-ключа при рефакторинге. Кроме того, часто требуется построить функцию, принимающую только корректные ключи конкретного объекта или типа.
Решение
Оператор keyof создает union-тип из всех ключей типа. С его помощью можно ограничивать допустимые ключи, поднимать безопасность API (например, для getter/setter функций), и создавать универсальные утилитарные типы.
Пример кода:
type User = { name: string; age: number; active: boolean }; type UserKey = keyof User; // "name" | "age" | "active" function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user: User = { name: 'Tom', age: 33, active: true }; const age = getProp(user, 'age'); // возраст типа number
Ключевые особенности:
Что возвращает keyof у массива и можно ли получить индексы?
keyof для массива возвращает типы ключей объекта, которыми массив на самом деле и является в JS. Обычно это строки индексов и специальные свойства массива.
Пример кода:
type A = keyof number[]; // "length" | "toString" | "pop" | ... | number
Может ли keyof вернуть ключи у union-types?
Да, keyof от union-типа возвращает пересечение ключей обоих объектов, а не их объединение.
Пример кода:
type A = {a: string, b: number } type B = {b: number, c: boolean } type C = keyof (A | B); // "b"
Что произойдет если свойство объекта опциональное? Поддерживает ли keyof "?"?
Да, опциональные свойства тоже входят в результирующий тип ключей, но это не означает, что свойство обязательно присутствует у объекта в рантайме.
Пример кода:
type D = { a: string; b?: number }; type DK = keyof D; // "a" | "b"
Пишут функцию getProperty(obj, key) с ключом строкового типа, вызывают с несуществующим ключом — ошибка появляется только во время выполнения.
Плюсы:
Минусы:
Используют дженерики: getProperty<T, K extends keyof T>(), тип key строго ограничен ключами структуры.
Плюсы:
Минусы: