Урок по треку TypeScript 6 мин

Утилитарные типы и как они устроены

Partial, Pick, Omit, Record, ReturnType, Awaited и как они устроены.

Утилитарные типы встроены в 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 и почему он работает по элементам объединения.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.

Вопросы модуля (45)