Утилитарные типы встроены в TypeScript и описаны в lib.es5.d.ts обычными mapped и условными типами. На собесе часто просят не только назвать их, но и написать реализацию.
Работа с полями объекта
type User = { id: number; name: string; email?: string };
Partial<User> // все поля необязательны
Required<User> // все поля обязательны, ? снят
Readonly<User> // все поля readonly (поверхностно)
Pick<User, 'id' | 'name'> // только эти поля
Omit<User, 'email'> // все, кроме этих
Record<'ru' | 'en', string> // объект с ключами ru, en и значениями string
Реализации:
type MyPartial<T> = { [K in keyof T]?: T[K] };
type MyRequired<T> = { [K in keyof T]-?: T[K] };
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type MyRecord<K extends keyof any, V> = { [P in K]: V };
Обратите внимание: у Omit ключи не ограничены keyof T, поэтому Omit<User, 'опечатка'> компилируется молча. Pick с несуществующим ключом даст ошибку.
Работа с объединениями
type Status = 'idle' | 'loading' | 'done' | null;
Exclude<Status, null> // 'idle' | 'loading' | 'done'
Extract<Status, 'idle' | 'done'> // 'idle' | 'done'
NonNullable<string | null | undefined> // string
Exclude<T, U> = T extends U ? never : T. Работает поэлементно благодаря распределению условного типа по объединению.
Работа с функциями и промисами
function load(id: number, force?: boolean): Promise<User> { ... }
Parameters<typeof load> // [id: number, force?: boolean]
ReturnType<typeof load> // Promise<User>
Awaited<ReturnType<typeof load>> // User
ConstructorParameters<typeof Date>
InstanceType<typeof Map>
ReturnType принимает тип функции, а не саму функцию, поэтому нужен typeof. Awaited распаковывает промисы рекурсивно: Awaited<Promise<Promise<number>>> это number.
Строковые
Uppercase, Lowercase, Capitalize, Uncapitalize работают со строковыми литералами: Capitalize<'click'> это 'Click'.
NoInfer
С TypeScript 5.4 NoInfer<T> запрещает выводить параметр из этой позиции:
function pick<C extends string>(colors: C[], initial: NoInfer<C>) {}
pick(['red', 'blue'], 'green'); // ошибка, 'green' не расширяет C
Частые ошибки
- Считать
ReadonlyиPartialглубокими. Они действуют только на верхний уровень. ReturnType<load>безtypeof.Omitна объединении:Omit<A | B, 'x'>склеивает варианты в один объект с общими ключами, дискриминация пропадает. Нужен распределённый вариант.
Что спросят на собесе
- Назовите пять утилит и напишите реализацию
PickилиPartial. - Чем
Omitотличается отExclude. - Как получить тип результата асинхронной функции.
- Глубокий ли
Readonly. - Как устроен
Excludeи почему он работает по элементам объединения.