Продвинутые утилиты
Собрали набор утилит, которые часто нужны в проектах.
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 — для типобезопасных путей