Что такое mapped types в TypeScript?

SeniorTypeScript · Frontend·Обновлено 2 июля 2026
Коротко
Mapped types — это механизм TypeScript, позволяющий создавать новые типы путём преобразования каждого свойства существующего типа по заданному правилу. Они используют синтаксис [K in keyof T] для итерации по ключам типа.

Mapped Types в TypeScript

Mapped types (отображаемые типы) позволяют создавать новые типы на основе существующих, применяя преобразование к каждому свойству исходного типа. Синтаксически они напоминают цикл for...in, но на уровне системы типов.

Базовый синтаксис

Основа mapped type — конструкция [K in keyof T], где K перебирает все ключи типа T:

type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

type Partial<T> = {
  [K in keyof T]?: T[K];
};

Модификаторы свойств

С помощью + и - можно добавлять или удалять модификаторы readonly и ?:

// Удаляем readonly со всех свойств
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

// Делаем все свойства обязательными
type Required<T> = {
  [K in keyof T]-?: T[K];
};

Remapping ключей через as

TypeScript 4.1 добавил возможность переименовывать ключи с помощью as:

// Добавляем префикс get к каждому свойству
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type User = { name: string; age: number };
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }

Фильтрация свойств через never

Если remapping вернёт never, свойство исключается из результирующего типа:

// Оставляем только строковые свойства
type StringProperties<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type User = { name: string; age: number; email: string };
type StringUser = StringProperties<User>;
// { name: string; email: string }

Практические примеры

Mapped types лежат в основе большинства утилитных типов стандартной библиотеки:

// Pick — выбираем подмножество свойств
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// Record — создаём объект с заданными ключами и типом значений
type MyRecord<K extends keyof any, V> = {
  [P in K]: V;
};

// Глубокий Readonly
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

Дистрибутивность и union-типы

При итерации по union-типу mapped type обрабатывает каждый элемент отдельно:

type Flags<T extends string> = {
  [K in T]: boolean;
};

type FeatureFlags = Flags<'darkMode' | 'beta' | 'premium'>;
// { darkMode: boolean; beta: boolean; premium: boolean }

Связь с Conditional Types

Mapped types часто комбинируются с условными типами для создания мощных преобразований:

// Делаем свойства опциональными только если их тип включает undefined
type SmartPartial<T> = {
  [K in keyof T]: undefined extends T[K] ? T[K] : T[K] | undefined;
};

Mapped types — один из ключевых инструментов для создания переиспользуемых абстракций над типами и реализации принципа DRY на уровне системы типов.

Что хочет услышать интервьюер

Понимание синтаксиса `[K in keyof T]` и того, как TypeScript итерирует по ключам типа

Знание модификаторов `+`/`-` для `readonly` и `?`, умение объяснить разницу между добавлением и удалением

Знакомство со стандартными утилитными типами (`Partial`, `Required`, `Readonly`, `Pick`, `Record`) и понимание их реализации через mapped types

Знание remapping через `as` (TypeScript 4.1+) и фильтрации свойств через `never`

Способность показать практический сценарий применения: трансформация API-ответов, построение форм, типизация конфигураций

Пример: Модификаторы +/- в mapped types

// Базовый mapped type — делаем все свойства опциональными
type Partial<T> = {
  [K in keyof T]?: T[K];
};

// Удаляем опциональность и readonly
type Concrete<T> = {
  -readonly [K in keyof T]-?: T[K];
};

type Config = {
  readonly host?: string;
  readonly port?: number;
};

type StrictConfig = Concrete<Config>;
// { host: string; port: number }  — ни readonly, ни ?

Пример: Remapping ключей через as

// Remapping ключей через as (TypeScript 4.1+)
type EventMap<T> = {
  [K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;
};

type UserState = {
  name: string;
  age: number;
  loggedIn: boolean;
};

type UserEvents = EventMap<UserState>;
// {
//   onName: (value: string) => void;
//   onAge: (value: number) => void;
//   onLoggedIn: (value: boolean) => void;
// }

Пример: Фильтрация через never

// Фильтрация свойств: оставляем только функции
type MethodsOnly<T> = {
  // eslint-disable-next-line @typescript-eslint/ban-types
  [K in keyof T as T[K] extends Function ? K : never]: T[K];
};

class UserService {
  name = 'UserService';
  version = 1;

  getUser(id: number) { return { id }; }
  deleteUser(id: number) { return true; }
}

type ServiceMethods = MethodsOnly<UserService>;
// { getUser: (id: number) => ...; deleteUser: (id: number) => ... }
// name и version отфильтрованы через never

Типичные ошибки

Путают mapped types с index signatures — `{ [key: string]: T }` не является mapped type, так как не итерирует по конкретным ключам

Не знают про модификаторы `-readonly` и `-?`, думая, что нельзя убрать уже установленные модификаторы

Забывают, что remapping через `as` появился только в TypeScript 4.1 — предлагают несуществующий синтаксис для старых версий

Не могут объяснить, как `never` в `as`-клаузе приводит к исключению свойства из типа

Смешивают mapped types с generic-типами, не понимая, что mapped type — это конструкция внутри тела типа, а не сам по себе generic

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее