Как типизировать объект с разными ключами и значениями в TypeScript?
Что проверяют
Знает ли кандидат больше одного способа и понимает ли главный побочный эффект индексной сигнатуры: она отключает проверку имён ключей.
Как отвечать
Начать стоит с уточнения: набор ключей известен заранее или он произвольный. От этого зависит правильный инструмент.
Ключи известны. Обычный тип или интерфейс, у каждого поля свой тип. Самый сильный вариант, опечатка в имени ловится компилятором.
type User = { name: string; age: number; isActive: boolean };Ключи из конечного множества. Record с union ключей, компилятор потребует заполнить все варианты и подсветит забытый при добавлении нового статуса.
type Status = "new" | "active" | "banned";
const labels: Record<Status, string> = {
new: "Новый",
active: "Активен",
banned: "Заблокирован",
};Ключей произвольно много. Индексная сигнатура или Record<string, T>:
interface NumericMap { [key: string]: number }Здесь нужна оговорка: с индексной сигнатурой доступ по любому ключу считается валидным, и scores.alise пройдёт типизацию, вернув number, хотя значения нет. Лечится флагом noUncheckedIndexedAccess, после которого тип результата становится number | undefined.
Значение зависит от ключа. Вместо string | number | boolean на всё подряд берётся mapped type, тогда связь ключа и типа значения сохраняется:
type FieldTypes = { name: string; age: number };
type Form = { [K in keyof FieldTypes]: { value: FieldTypes[K]; touched: boolean } };Для функций доступа ту же связь держит дженерик:
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}Полезно упомянуть и остальной арсенал: дискриминированные объединения, когда форма объекта зависит от поля kind; шаблонные литеральные типы, когда ключи строятся по правилу; оператор satisfies для проверки литерала без потери точного типа; Record<string, unknown> вместо any для данных из внешнего источника; утилиты Partial, Pick, Omit поверх базового типа.
Где спотыкаются
Останавливаются на [key: string]: any и не могут объяснить два следствия индексной сигнатуры: все явно объявленные свойства обязаны укладываться в её тип, а опечатка в ключе перестаёт быть ошибкой компиляции.