Урок 08

Mapped types

Mapped type преобразует все ключи типа по одному шаблону.

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

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

Проходит по всем ключам T и создаёт новый тип с теми же ключами.

Модификация значений

type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

type User = { name: string; age: number };
type NullableUser = Nullable<User>;
// { name: string | null; age: number | null }

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

Знак ? добавляет опциональность:

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

Знак -? убирает:

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

readonly добавляет:

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

-readonly убирает:

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

Переименование ключей

Через as:

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 }

Фильтрация ключей

Если as даёт never — ключ удаляется:

type OnlyStrings<T> = {
    [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type User = { name: string; age: number; email: string };
type StringFields = OnlyStrings<User>;
// { name: string; email: string }

По union ключей

type PickByKeys<T, K extends keyof T> = {
    [P in K]: T[P];
};

type User = { name: string; age: number; email: string };
type NameAndEmail = PickByKeys<User, 'name' | 'email'>;

Это встроенный Pick.

По union значений

type Status = 'idle' | 'loading' | 'success';

type StatusMap = {
    [K in Status]: boolean;
};
// { idle: boolean; loading: boolean; success: boolean }

Встроенные mapped types

Все утилиты Partial, Required, Readonly, Pick, Record — это mapped types.

type MyPick<T, K extends keyof T> = {
    [P in K]: T[P];
};

type MyRecord<K extends keyof any, V> = {
    [P in K]: V;
};

Практика: Optional

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type User = { name: string; age: number; email: string };
type UserWithOptionalEmail = Optional<User, 'email'>;

Практика: DeepPartial

type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

Практика: DeepReadonly

type DeepReadonly<T> = {
    readonly [K in keyof T]: T[K] extends object
        ? DeepReadonly<T[K]>
        : T[K];
};

Практика: FormErrors

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

type UserForm = { name: string; email: string };
type Errors = FormErrors<UserForm>;
// { name?: string; email?: string }

Практика: Setters

type Setters<T> = {
    [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

Итоги

  • Mapped types преобразуют все ключи
  • [K in keyof T] — база
  • ?, -?, readonly, -readonly — модификаторы
  • as переименовывает или фильтрует
  • Встроенные утилиты — это mapped types
  • Полезны для глубоких преобразований