Урок 10

Продвинутые утилиты

Собрали набор утилит, которые часто нужны в проектах.

DeepPartial

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

Все поля вложенных объектов становятся опциональными.

DeepReadonly

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

DeepRequired

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

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'>;

RequireAtLeastOne

Хотя бы одно поле обязательно:

type RequireAtLeastOne<T, K extends keyof T = keyof T> =
    Omit<T, K> & {
        [P in K]-?: Required<Pick<T, P>> & Partial<Omit<T, P>>;
    }[K];

type Filters = RequireAtLeastOne<{ name?: string; age?: number }>;

const f1: Filters = { name: 'Алиса' };
const f2: Filters = { age: 25 };
const f3: Filters = {}; // ошибка

Mutable

Убрать readonly:

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

Nullable

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

ValueOf

Union всех значений типа:

type ValueOf<T> = T[keyof T];

const Status = {
    Idle: 'idle',
    Loading: 'loading',
} as const;

type Status = ValueOf<typeof Status>;
// 'idle' | 'loading'

NonEmptyArray

type NonEmptyArray<T> = [T, ...T[]];

function head<T>(arr: NonEmptyArray<T>): T {
    return arr[0];
}

head([1, 2, 3]); // ок
head([]);        // ошибка

Brand

Номинальная типизация (чтобы нельзя было передать обычный string):

type UserId = string & { __brand: 'UserId' };

function getUser(id: UserId) {}

getUser('abc' as UserId); // ок

UnionToIntersection

type UnionToIntersection<U> =
    (U extends any ? (arg: U) => void : never) extends (arg: infer I) => void
        ? I
        : never;

TupleToUnion

type TupleToUnion<T extends readonly any[]> = T[number];

type A = TupleToUnion<[string, number, boolean]>;
// string | number | boolean

Paths

Пути вложенных свойств:

type Paths<T> = T extends object
    ? {
          [K in keyof T]: K | `${string & K}.${string & Paths<T[K]>}`;
      }[keyof T]
    : never;

At

Доступ к вложенному свойству:

type At<T, P extends string> =
    P extends `${infer K}.${infer R}`
        ? K extends keyof T
            ? At<T[K], R>
            : never
        : P extends keyof T
            ? T[P]
            : never;

Итоги

  • Deep* — глубокие преобразования
  • Optional — выборочная опциональность
  • RequireAtLeastOne — контроль заполнения
  • NonEmptyArray — непустой массив
  • Brand — номинальные типы
  • Paths — для типобезопасных путей