Keyof в TypeScript: тип ключа и чтение свойства объекта

Keyof в TypeScript: тип ключа и чтение свойства объекта
Содержание статьи

keyof получает тип допустимых ключей из типа объекта. Вместе с индексируемым типом T[K] он помогает написать функцию чтения свойства: переданный ключ должен существовать в типе объекта, а результат сохраняет тип конкретного поля. Это проверка на этапе компиляции, не валидация внешнего JSON.

От типа объекта к типу ключа

У типа User два известных поля. Поэтому UserKey допускает только строковые литералы id и name.

type User = { id: number; name: string };
type UserKey = keyof User; // "id" | "name"

const user = { id: 7, name: "Анна" };
type ActualKey = keyof typeof user; // "id" | "name"

В первом случае User уже является типом. Во втором user является значением, поэтому typeof user сначала извлекает его тип, а keyof получает ключи. Это использование typeof в позиции типа; оно не создаёт проверку объекта во время выполнения программы.

Функция, связывающая ключ и результат

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const id = getProperty(user, "id");     // number
const name = getProperty(user, "name"); // string
// getProperty(user, "email"); // ошибка проверки типов

Ограничение K extends keyof T связывает второй аргумент с первым. Возвращаемый T[K] обозначает тип свойства по этому ключу. Если вместо конкретного литерала передать ключ с объединением "id" | "name", результат будет number | string: компилятор не обязан знать, какое поле будет выбрано при выполнении.

Не расширяйте переменную ключа до произвольного string без необходимости. У значения const key = "name" тип достаточно узкий. Данные, пришедшие из адресной строки или формы, имеют другой уровень доверия и требуют проверки до вызова такой функции.

Индексная сигнатура меняет набор ключей

Тип { [key: string]: boolean } описывает произвольные строковые ключи, а его keyof имеет тип string | number. Причина числовой части связана с обращением к свойствам JavaScript: числовой ключ обычного объекта приводится к строке. Не переносите вывод о конечном наборе «id или name» на словарь с индексной сигнатурой.

Также Object.keys() во время выполнения и keyof на уровне типов решают разные задачи. Простое приведение результата Object.keys к массиву keyof может скрыть расхождение фактического объекта и его статического типа. Сначала определите, откуда объект пришёл и что гарантирует его форму.

Граница проверки

Внешний JSON нужно проверить отдельно: существует ли объект, какие поля присутствуют и имеют ли они нужные типы. Утверждение as User не превращает непроверенные данные в корректного пользователя. Для функции выше полезно проверить вывод number и string, ошибку несуществующего ключа и объединение результатов при ключе-переменной.

На курсе TypeScript можно последовательно освоить такие связи типов. Если generic-функция уже разрослась, наставник по TypeScript на Mentoring.digital поможет отделить проверяемый контракт от небезопасных приведений.

Похожие статьи по данной теме