Какие альтернативы использованию infer в TypeScript для типизации ключей?
Что проверяют
Понимаете ли вы, что infer нужен для извлечения типа из структуры внутри условного типа, а для работы с ключами обычно хватает более простых средств. Следом часто просят написать типизированный get(obj, key) или тип, выбирающий ключи по типу значения.
Как отвечать
Сначала разведите задачи: получить ключи, связать ключ со значением, отфильтровать или переименовать ключи. Под каждую есть инструмент без infer:
keyof: объединение ключей типа,keyof Userдаёт"id" | "name";- индексный доступ
T[K]: тип значения по ключу, аT[keyof T]даёт объединение всех значений; - дженерик с ограничением:
function get<T, K extends keyof T>(obj: T, key: K): T[K]связывает ключ и возвращаемый тип без единого условного типа; - mapped types:
{ [K in keyof T]: ... }, а с ремаппингом черезas(TypeScript 4.1+) ключи можно переименовать или выкинуть, вернувnever; - условный тип без
infer:{ [K in keyof T]: T[K] extends string ? K : never }[keyof T]выбирает ключи со строковыми значениями; - template literal types: `
on${Capitalize<K>}` строит имена обработчиков из ключей; as constиsatisfies: сохраняют литеральные ключи конфига,satisfies Record<string, Route>проверяет форму объекта и не расширяет его ключи доstring.
Хорошо упомянуть, что встроенные ReturnType, Parameters и Awaited сами написаны через infer: пользоваться ими и есть самый частый способ обойтись без своего.
Где спотыкаются
Ждут, что Object.keys(obj) вернёт (keyof T)[]. Он возвращает string[] намеренно: из-за структурной типизации в объекте могут оказаться лишние поля. Приведение типа здесь должно быть осознанным.
Забывают, что keyof от типа с индексной сигнатурой Record<string, X> даёт string | number, а от объединения типов только общие ключи.
Путают Record<K, V> с keyof: Record не извлекает ключи, а строит объект из уже известного набора ключей.