Интервью-вопрос
Что такое Mapped Types
Mapped Types позволяют строить новые типы из ключей существующего типа. На интервью важно назвать синтаксис и сразу уточнить, что это compile-time инструмент, а не runtime-преобразование данных.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 70 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
Mapped Type решает простую задачу. У вас уже есть объектный тип, и вы хотите получить другой тип с теми же ключами или с преобразованными ключами. Вместо копирования полей вручную вы описываете правило для каждого ключа.
type User = {
id: number;
name: string;
isAdmin: boolean;
};
type FieldFlags<T> = {
[K in keyof T]: boolean;
};
type UserFlags = FieldFlags<User>;
// {
// id: boolean;
// name: boolean;
// isAdmin: boolean;
// }В ответе полезно назвать три части. keyof T получает union ключей. K по очереди представляет каждый ключ. T[K] достает тип значения по этому ключу. Так вы показываете механику, а не просто вспоминаете название Partial.
Как это сказать на интервью
Хороший короткий ответ может звучать так:
Mapped Types это механизм TypeScript для создания новых объектных типов по ключам существующего типа. Например, можно пройтись по
keyof Tи сделать все поля optional, readonly или заменить тип каждого значения. Так работают многие Utility Types, напримерPartialиReadonly. Это проверка типов на этапе компиляции, а не преобразование объекта в runtime.
Если хотите усилить ответ, добавьте пример из frontend-задачи. Это может быть тип тела PATCH запроса, состояние формы, readonly DTO из API или генерация геттеров для модели. Так ответ звучит практично, а не как пересказ документации.
Синтаксис и модификаторы
Базовый синтаксис можно читать как цикл по ключам типа. Но важно не называть его runtime-циклом. Это правило для компилятора TypeScript.
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};У mapped types есть управление модификаторами. Плюс обычно можно не писать. Добавление модификатора используется по умолчанию. Минус нужен, когда вы хотите явно снять модификатор.
type MutableRequired<T> = {
-readonly [K in keyof T]-?: T[K];
};
type ApiUser = {
readonly id?: number;
readonly name?: string;
};
type EditableUser = MutableRequired<ApiUser>;
// { id: number; name: string }Практический вывод простой. Если вы типизируете форму редактирования, случайно оставленный readonly помешает обновлять поля. Случайно оставленный ? разрешит пропустить значение, которое UI уже должен требовать.
Как выбрать подход
На интервью не нужно сразу писать сложный generic. Часто сильнее выглядит простой тип под задачу. Если задача стандартная, используйте встроенную утилиту. Если есть проектное правило, пишите свой mapped type.
Как выбрать типовое преобразование
Начните со встроенных Utility Types: Partial, Required, Readonly.Используйте Pick или Omit, не дублируйте интерфейс руками.Пишите свой Mapped Type, например Nullable<T> или Flags<T>.Используйте key remapping через as и проверяйте number и symbol ключи.Разделите DTO, форму и patch body. Не делайте весь экран одним Partial<Dto>, иначе легко пропустить обязательное поле в UI.Mapped Type не поможет в runtime. Нужна валидация, безопасный парсер или явная проверка перед рендером.Пример для API и форм
Самый понятный frontend-пример связан с DTO и запросами. Представьте, что у вас есть профиль. В PATCH запрос можно отправлять только редактируемые поля.
type ProfileDto = {
id: string;
name: string;
email: string;
createdAt: string;
};
type ProfilePatch = Partial<Pick<ProfileDto, "name" | "email">>;
async function updateProfile(userId: string, patch: ProfilePatch) {
if (Object.keys(patch).length === 0) {
return;
}
await fetch(`/api/users/${userId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(patch),
});
}Здесь Pick ограничивает набор полей. Partial разрешает отправить только измененную часть. Проверка на пустой объект не относится к mapped types, но нужна для UX. Иначе кнопка сохранения может отправить лишний запрос без изменений. Если заменить тип на Partial<ProfileDto>, он разрешит случайно отправить id или createdAt. Это может привести к ошибке API, конфликту данных или тихому игнорированию лишних полей. Безопаснее держать отдельный ProfilePatch и не отправлять запрос, если изменений нет.
Типы для UI-состояния
Mapped Types часто появляются рядом с React-формами. Здесь важно не превратить удобный тип для запроса в слабый тип для всего экрана.
type ProfileDto = {
id: string;
name: string;
email: string;
createdAt: string;
};
type ProfileForm = Pick<ProfileDto, "name" | "email">;
type ProfilePatch = Partial<ProfileForm>;
type ProfileViewState =
| { status: "loading" }
| { status: "error"; message: string }
| { status: "ready"; data: ProfileDto; form: ProfileForm };Такой подход помогает UI не гадать, почему поля нет. Если экран еще грузится, это status: "loading". Если запрос упал, это status: "error". Если данные готовы, поля формы обязательны. Так меньше риска показать пустое имя, отправить лишний ключ или спрятать ошибку за optional свойством.
Переименование и фильтрация ключей
Более сильный уровень ответа это key remapping через as. Он позволяет не только менять значения свойств, но и создавать новые имена ключей или исключать часть ключей.
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<{
name: string;
age: number;
}>;
// {
// getName: () => string;
// getAge: () => number;
// }
type WithoutId<T> = {
[K in keyof T as K extends "id" ? never : K]: T[K];
};Фильтрация работает через never. Если ключ переименован в never, он исчезает из итогового типа. В примере с геттерами используется string & K, потому что keyof может включать не только строки, но и числа или символы.
Главная граница применения
Mapped Types не защищают от плохих данных из сети. Они помогают вам описать ожидаемый контракт, получить автодополнение и поймать ошибки в коде до запуска. Но если сервер вернул {"name": null}, а тип обещал name: string, TypeScript сам не остановит выполнение в браузере.
Безопасная формулировка на интервью такая. Типы помогают на этапе разработки. Для внешних данных нужны runtime-проверки, аккуратная обработка unknown, схемы валидации или проверка обязательных полей перед использованием в UI. Без этой проверки можно показать неверный empty state, сломать рендер на null, отправить повторный запрос после неясной ошибки или записать в аналитику некорректное действие пользователя.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Путать типовое преобразование с runtime-логикой
Mapped Type не вызывает
Object.keysи не меняет объект в браузере. Если вы скажете, чтоPartialудаляет поля, вас быстро спросят про скомпилированный JavaScript. Корректнее сказать, что TypeScript меняет только проверку типов. - 2
Терять модификаторы свойств
В mapped type можно случайно оставить
readonlyили?там, где нужен нормализованный тип формы. Используйте-readonlyи-?, если хотите явно снять эти модификаторы. Иначе UI может выглядеть безопасным, но тип не даст обновить поле или разрешит пропустить обязательное значение. - 3
Писать сложный тип вместо встроенной утилиты
Если нужен обычный PATCH body, часто достаточно
Partial,Pickили их комбинации. Самописный тип без причины повышает шум и усложняет ревью. На интервью лучше показать, что вы знаете стандартные утилиты и пишете свой mapped type только для отдельного правила проекта. - 4
Использовать Partial как состояние всего экрана
Partial<UserDto>удобен для PATCH запроса, но опасен как общий тип состояния React-компонента. Компонент начнет считать, что любое поле может отсутствовать в любой момент. В итоге можно получить пустые labels, неправильный empty state или падение наuser.name.toUpperCase().Безопаснее разделить типы. Отдельно ответ API, нормализованная модель для рендера, модель формы и тело запроса. Тогда loading, error и empty state выражаются явно, а не прячутся за optional полями.
- 5
Применять к неподходящим типам
Mapped Types удобны для объектных типов. С примитивами, функциями и массивами результат может быть не тем, что вы ожидали. Например, у массива есть числовые индексы и методы, а не только элементы. Для таких случаев лучше выбрать отдельный тип модели или явно ограничить generic через
extends object. - 6
Забывать про ключи number и symbol
При генерации строковых имен через template literal types нельзя слепо считать, что каждый ключ это string. Часто используют
string & K, чтобы работать только со строковыми ключами. Без этого тип может стать слишком широким или перестать компилироваться на DTO с нестандартными ключами.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которые вам могут задать после основного ответа.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что такое keyof 😎
keyof в TypeScript получает union ключей типа и помогает безопасно работать со свойствами объектов. Разбираем, чем он отличается от Object.keys, как сочетается с generics и где легко ошибиться.
Что такое Pick в TypeScript 😎
Pick создает новый тип из выбранных свойств исходного типа. Разбираем, как объяснить его на интервью, когда применять в React и чем он отличается от Omit.