Интервью-вопрос
Что такое keyof
keyof строит union ключей типа и помогает безопасно обращаться к свойствам. Главный риск в ответе, спутать его с Object.keys и не показать связь с generics.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 50 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
keyof отвечает на вопрос: какие ключи есть у типа. Если есть тип пользователя, TypeScript может построить из его свойств отдельный union-тип.
type User = {
id: string;
name: string;
age: number;
};
type UserKey = keyof User;
// "id" | "name" | "age"Это не JavaScript-код и не операция над реальным объектом. После компиляции keyof исчезает. Его польза в том, что ошибка с несуществующим ключом ловится до запуска приложения.
Как это сказать на интервью
Короткий ответ может звучать так:
keyof это оператор TypeScript, который берет тип объекта и возвращает union его ключей. Его часто используют вместе с generics, чтобы принимать только допустимые имена свойств. Если нужно вернуть значение по ключу, связь сохраняют через K extends keyof T и T[K].
После этого сразу добавьте отличие от Object.keys(). Так вы покажете, что понимаете границу между типами и runtime. Это сильнее, чем просто привести пример с 'name' | 'age'.
Практический пример с generic-функцией
Самый частый практический пример: безопасный getter. Он полезен в UI-коде, где вы строите таблицу, форму или фильтр по имени поля.
type User = {
id: string;
name: string;
age: number;
};
function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: "u1", name: "Anna", age: 28 };
const name = getField(user, "name");
// string
const age = getField(user, "age");
// number
getField(user, "email");
// error: "email" не является ключом UserЗдесь K нужен не для красоты. Он сохраняет конкретный ключ, который вы передали. Поэтому getField(user, "age") возвращает number, а не string | number.
Где keyof особенно полезен
Как выбрать применение keyof
Используйте K extends keyof T и возвращайте T[K].Используйте mapped type: [K in keyof T].Используйте Object.keys и отдельно подумайте о типизации результата.Не доверяйте Object.keys как списку безопасных колонок. Проверьте форму данных или используйте заранее заданный whitelist ключей.Сначала получите тип через typeof, затем примените keyof typeof value.Во frontend-разработке keyof часто появляется в типах колонок таблицы, полях формы, сортировке, настройках фильтров и typed helper-функциях. Идея простая: если UI ссылается на поле модели, TypeScript должен запретить опечатку.
type Product = {
title: string;
price: number;
inStock: boolean;
};
type Column<T> = {
key: keyof T;
label: string;
};
const columns: Column<Product>[] = [
{ key: "title", label: "Название" },
{ key: "price", label: "Цена" },
// { key: "cost", label: "Стоимость" }, // error
];Практический вывод простой. При переименовании поля модели TypeScript подсветит места, где UI еще обращается к старому ключу. Это снижает риск сломанной таблицы, пустого значения в форме или неверной сортировки.
keyof и mapped types
keyof часто используют внутри mapped types. Так TypeScript проходит по ключам исходного типа и создает новый тип с теми же полями, но с другими правилами.
type ValidationState<T> = {
[K in keyof T]: {
value: T[K];
error?: string;
};
};
type LoginForm = {
email: string;
password: string;
};
type LoginValidation = ValidationState<LoginForm>;В этом примере email остается связанным со string, а не превращается в случайное поле. Для форм это важно. Иначе легко потерять тип значения и начать валидировать число как строку или наоборот.
Чем keyof отличается от Object.keys
keyof и Object.keys() похожи по смыслу, но работают на разных уровнях.
type User = {
id: string;
name: string;
};
type UserKey = keyof User;
// "id" | "name"
const user = { id: "u1", name: "Anna" };
const keys = Object.keys(user);
// string[]Object.keys() возвращает строки из реального объекта во время выполнения. TypeScript обычно не делает результат Array<keyof User>, потому что runtime-объект может быть шире типа, а внешние данные могут содержать неожиданные поля.
Если вы приводите результат к Array<keyof T>, делайте это осознанно. Для закрытого локального объекта это может быть нормальным компромиссом. Для ответа API лучше сначала проверить форму данных, иначе UI может обработать лишний ключ так, будто он безопасен.
Плохой вариант для таблицы, которая строится по ответу сервера:
type User = {
id: string;
name: string;
email: string;
};
function renderUserCells(user: User) {
return (Object.keys(user) as Array<keyof User>).map((key) => user[key]);
}Что сломается: если в runtime придет лишнее поле, например isAdmin или внутренний токен, таблица может показать его пользователю. TypeScript не защитит, потому что приведение типа уже сказало компилятору доверять всем ключам.
Безопаснее задавать список колонок явно и проверять, что данные подходят под ожидаемую форму:
const userColumns = ["name", "email"] satisfies Array<keyof User>;
function renderUserCells(user: User) {
return userColumns.map((key) => user[key]);
}Так UI рендерит только разрешенные поля. При переименовании поля TypeScript подсветит ошибку в списке колонок.
Нюансы, которые усиливают ответ
Не говорите, что keyof всегда возвращает только строки. В TypeScript ключи свойств могут быть строковыми, числовыми и символьными.
type Dictionary = {
[key: string]: boolean;
};
type DictionaryKey = keyof Dictionary;
// string | numberПричина в поведении JavaScript. Доступ obj[1] и obj["1"] для обычного объекта связан с приведением ключа к строке. Поэтому для типов с index signature результат может быть шире, чем ожидается.
С массивами тоже нужна аккуратность. keyof string[] не означает только индексы элементов. Массив это объект с числовым доступом, length и методами. Если вам нужен тип элемента массива, чаще пишут T[number], а не используют keyof T.
keyof typeof для значений и enum
keyof принимает тип. Если у вас есть значение, например объект настроек, сначала нужен typeof.
const routes = {
home: "/",
profile: "/profile",
settings: "/settings",
} as const;
type RouteName = keyof typeof routes;
// "home" | "profile" | "settings"Та же идея часто встречается с enum:
enum Status {
Draft = "draft",
Published = "published",
}
type StatusName = keyof typeof Status;
// "Draft" | "Published"В ответе достаточно сказать, что typeof переводит значение в его тип, а keyof берет ключи уже у этого типа. Так вы покажете, что не смешиваете переменные и type aliases.
Практический вывод
На интервью не нужно превращать ответ в справочник по TypeScript. Держите структуру простой: что делает keyof, где применяется, чем отличается от Object.keys(), какие есть ограничения.
Если вопрос задают в контексте frontend-кода, привяжите ответ к реальному риску. Например, в таблице или форме опечатка в имени поля может дать пустую ячейку, сломанную сортировку или неверную отправку данных. keyof переносит эту ошибку из браузера в компилятор.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Путать типы и runtime
keyofне выполняется в браузере и не возвращает данные из объекта. Если вы скажете, что он получает массив ключей, ответ быстро сломается на сравнении сObject.keys(). Надежнее формулировать так:keyofстроит тип, аObject.keys()читает объект во время выполнения. - 2
Забывать про T[K]
Сам по себеkeyofограничивает ключи, но тип значения получается черезT[K]. Если вернуть простоT[keyof T], результат станет union всех возможных значений, и код потеряет точность. В getter-функции ключ лучше выносить в отдельный genericK. - 3
Считать все ключи строками
В TypeScript ключ свойства может бытьstring,numberилиsymbol. Для index signature и массивов это особенно заметно. Если функция реально принимает только строковые ключи, это нужно выразить типом, например черезExtract<keyof T, string>. - 4
Небезопасно типизировать Object.keys
Частая ошибка: сразу привестиObject.keys(obj)кArray<keyof T>и забыть, что runtime-объект может содержать дополнительные свойства. Такое приведение может скрыть баг при работе с внешними данными. В UI это часто дает лишнюю колонку, пустую ячейку или показ поля, которое пользователь не должен видеть. Для данных с API сначала валидируйте форму объекта или проверяйте ключи по whitelist. - 5
Использовать keyof value без typeof
keyofпринимает тип, а не значение. Если у вас есть объектconfigили enum, пишитеkeyof typeof config. Иначе ответ звучит так, будто вы не разделяете пространство значений и пространство типов в TypeScript.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которыми проверяют понимание keyof, generics и runtime-ограничений.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что такое instanceof в TypeScript 😎
instanceof проверяет объект во время выполнения по цепочке прототипов и помогает TypeScript сузить тип в ветке условия. Разбираем, где он уместен, где не работает и чем заменить его для интерфейсов, примитивов и данных из API.
Что такое Mapped Types 😎
Mapped Types создают новый тип по ключам существующего типа и меняют свойства без дублирования. Разбираем синтаксис, связь с Utility Types, key remapping и риски для DTO и UI-состояния.