← Назад к вопросам

Что такое Mapped Types?

2.0 Middle🔥 141 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Mapped Types (Сопоставленные Типы) в TypeScript?

Mapped Types — это мощный механизм в TypeScript, позволяющий создавать новые типы на основе существующих, путем итерации по ключам (свойствам) исходного типа и их трансформации. По сути, это инструмент для программирования на уровне системы типов, который помогает избежать дублирования кода в определениях типов, обеспечивая при этом гибкость и безопасность.

Основной принцип работы

Mapped Types используют синтаксис, напоминающий цикл for...in в JavaScript, но применяемый к ключам типа. Базовая структура выглядит так:

type MappedType<T> = {
  [P in keyof T]: T[P];
};

Здесь:

  • keyof T — создает объединение (union) всех ключей (имен свойств) типа T.
  • P in — итерация по каждому ключу в этом объединении.
  • T[P] — получение типа значения свойства P из исходного типа T. Это индексируемый доступ (indexed access type).
  • Результат — новый объектный тип с теми же ключами и типами значений, что и у T. На данном примере это точная копия T.

Ключевые возможности и модификаторы

Сила Mapped Types раскрывается при использовании модификаторов readonly и ? (опциональность), а также операторов + и - для их явного добавления или удаления.

// Сделать все свойства только для чтения
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

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

// Удалить опциональность (сделать обязательными)
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// Удалить модификатор readonly
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Практические примеры использования

  1. Глубокая трансформация (Deep Mapped Types): Часто требуется применить преобразование рекурсивно ко всем вложенным объектам.

    type DeepPartial<T> = {
      [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
    };
    
    interface User {
      id: number;
      profile: {
        name: string;
        age: number;
      };
    }
    
    const partialUser: DeepPartial<User> = {
      profile: { name: "Alex" } // age и id можно не указывать
    };
    
  2. Фильтрация ключей: Используя условные типы (extends), можно создавать типы на основе подмножества свойств.

    // Тип только для строковых свойств
    type StringProperties<T> = {
      [P in keyof T as T[P] extends string ? P : never]: T[P];
    };
    
    // Тип, который убирает из модели методы, оставляя только данные
    type DataOnly<T> = {
      [P in keyof T as T[P] extends Function ? never : P]: T[P];
    };
    
  3. Изменение имен ключей (Key Remapping): С помощью конструкции as в TypeScript 4.1+ можно создавать новые имена свойств.

    // Добавить префикс "get" к каждому ключу
    type Getters<T> = {
      [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
    };
    
    interface Person { name: string; age: number; }
    // Получится тип: { getName: () => string; getAge: () => number; }
    
  4. Создание гомоморфных и негомоморфных типов: Гомоморфные типы (как Readonly<T>) сохраняют модификаторы (readonly, ?) исходного типа. Негомоморфные — нет. Например, Record<K, V> не является гомоморфным — он создает новый тип "с нуля".

Встроенные Mapped Types в TypeScript

TypeScript включает несколько встроенных утилитарных типов, реализованных через Mapped Types:

  • Readonly<T>, Partial<T>, Required<T> — как показано выше.
  • Pick<T, K> — создает тип, выбирая только указанные ключи K из T.
  • Record<K, T> — создает тип с ключами типа K и значениями типа T. По сути, это словарь или карта.
// Пример Pick и Record
interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type ProductPreview = Pick<Product, 'id' | 'name'>; // { id: number; name: string; }
type PageHeaders = Record<'home' | 'about', string>; // { home: string; about: string; }

Значение для разработчика

Использование Mapped Types кардинально повышает уровень абстракции при работе с типами. Вместо ручного описания похожих интерфейсов (User, UserPartial, UserReadonly) вы создаете одну основную модель и генерируете производные типы. Это:

  • Снижает количество ошибок и опечаток.
  • Упрощает рефакторинг — изменение основного типа автоматически обновит все производные.
  • Повышает читаемость и выразительность кода, явно указывая на намерения (например, "это частичная версия модели для обновления").
  • Открывает возможности для создания сложных, но типобезопасных абстракций, таких как валидаторы, сериализаторы или конфигурации для API-клиентов.

Таким образом, Mapped Types — это не просто синтаксический сахар, а фундаментальный инструмент для построения масштабируемых, легко поддерживаемых и строго типизированных приложений на TypeScript, позволяющий применять принципы DRY (Don't Repeat Yourself) на уровне системы типов.