Как сделать функцию getProperty с типизацией и проверкой ключей в TypeScript?
Что проверяют
Владение дженериками и keyof, и понимание границы между проверкой на этапе компиляции и проверкой в рантайме. Хороший кандидат сразу спрашивает, откуда берётся ключ: из кода или из внешних данных, потому что решения разные.
Как отвечать
Базовая форма, когда ключ известен компилятору:
function getProperty<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const userName = getProperty(user, "name"); // string
const userAge = getProperty(user, "age"); // numberВозвращаемый тип именно T[K], а не T[K] | undefined. Ограничение K extends keyof T уже не даёт передать чужой ключ, а лишний | undefined заставит все вызывающие места делать бессмысленные проверки и потеряет точность типа. Вызов getProperty(user, "city") не вернёт undefined, это ошибка компиляции: Argument of type '"city"' is not assignable to parameter of type '"name" | "age"'.
Проверка в рантайме нужна только там, где ключ приходит извне: из URL, формы, распарсенного JSON. Тогда сигнатура другая, а сужение делается через пользовательский type guard:
function hasKey<T extends object>(obj: T, key: PropertyKey): key is keyof T {
return Object.hasOwn(obj, key);
}
function getUnknownProperty<T extends object>(obj: T, key: string): T[keyof T] | undefined {
return hasKey(obj, key) ? obj[key] : undefined;
}Object.hasOwn (или Object.prototype.hasOwnProperty.call) лучше оператора in: in находит и унаследованные свойства, поэтому "toString" in obj истинно для любого литерала объекта, и проверка пропустит мусор.
Чем можно расширить ответ, если попросят:
noUncheckedIndexedAccessвtsconfig: с ним доступ по индексной сигнатуре (Record<string, V>) сам добавляет| undefined, и это честный способ получитьundefinedв типе, а не приписанный вручную;- вариант с дефолтом через перегрузку или второй параметр типа:
getProperty(obj, key, fallback): T[K] | D; - вложенный путь (
"user.address.city") типизируется шаблонными литеральными типами и рекурсивным условным типом, но в реальном коде это стоит дорого по времени компиляции.
Где спотыкаются
Пишут возврат T[K] | undefined и уверенно утверждают, что для несуществующего ключа функция вернёт undefined, хотя такой вызов до рантайма не доживёт: его отклонит компилятор.