Перейти к содержимому

Mapped Types

Способ программно строить типы по форме другого типа. На интервью обязательно: написать Partial, Readonly, Mutable, DeepPartial, NullableProps. Красный флаг — не знать про +/- модификаторы и as-клаузу для key remapping (TS 4.1).

Базовые маппинги и модификаторы readonly / ?:

type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Required2<T> = { [K in keyof T]-?: T[K] };
type Optional<T> = { [K in keyof T]+?: T[K] };
interface Conf { readonly host: string; port?: number }
type ConfM = Mutable<Conf>; // { host: string; port?: number }
type ConfR = Required2<Conf>; // { readonly host: string; port: number }

Key remapping через as + filtering по never:

// Префиксуем ключи
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
type UG = Getters<{ id: string; age: number }>;
// { getId: () => string; getAge: () => number }
// Фильтр: оставить только функции
type FunctionKeys<T> = {
[K in keyof T as T[K] extends (...a: any[]) => any ? K : never]: T[K]
};
type Only = FunctionKeys<{ id: string; save(): void; load(): Promise<void> }>;
// { save(): void; load(): Promise<void> }

Рекурсивный DeepPartial и сохранение массивов:

type DeepPartial<T> =
T extends (...a: any[]) => any ? T :
T extends readonly (infer U)[] ? readonly DeepPartial<U>[] :
T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
interface User { id: string; profile: { name: string; tags: string[] } }
type DP = DeepPartial<User>;
// { id?: string; profile?: { name?: string; tags?: readonly string[] } }

Итог: Mapped types + as-remapping + модификаторы дают полноценную алгебру преобразований object-типов.