Урок 07

Условные типы

Условный тип — это тернарный оператор на уровне типов: T extends U ? X : Y.

Базовый пример

type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false
type C = IsString<'hello'>; // true

Проверка на массив

type IsArray<T> = T extends any[] ? true : false;

type A = IsArray<string[]>; // true
type B = IsArray<string>;   // false

Выбор типа

type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<string[]>; // string
type B = ElementType<number[]>; // number
type C = ElementType<boolean>;  // boolean

infer U извлекает тип элемента массива.

С infer

infer объявляет переменную типа внутри условия:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = ReturnType<() => string>;         // string
type B = ReturnType<(x: number) => void>;  // void

Распространение на union

Условные типы распределяются по union:

type NonNullable<T> = T extends null | undefined ? never : T;

type A = NonNullable<string | null>;        // string
type B = NonNullable<string | null | number>; // string | number

Каждый элемент union проверяется отдельно, результаты объединяются.

Отключение распределения

Обернуть T в [T]:

type IsNever<T> = [T] extends [never] ? true : false;
type IsUnion<T> = T extends T ? ([T] extends [T] ? false : true) : never;

Вложенные условия

type TypeName<T> =
    T extends string ? 'string' :
    T extends number ? 'number' :
    T extends boolean ? 'boolean' :
    T extends any[] ? 'array' :
    'other';

type A = TypeName<string>;  // 'string'
type B = TypeName<number>;  // 'number'
type C = TypeName<string[]>; // 'array'

Практика: Unwrap

type Unwrap<T> = T extends Promise<infer U> ? U : T;

type A = Unwrap<Promise<string>>; // string
type B = Unwrap<number>;          // number

Вложенные промисы:

type DeepUnwrap<T> = T extends Promise<infer U> ? DeepUnwrap<U> : T;

type A = DeepUnwrap<Promise<Promise<string>>>; // string

Практика: FunctionKeys

type FunctionKeys<T> = {
    [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type User = {
    name: string;
    age: number;
    greet: () => void;
    logout: () => void;
};

type Fns = FunctionKeys<User>;
// 'greet' | 'logout'

Практика: RequiredKeys

type RequiredKeys<T> = {
    [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

Проверка на never

type IsNever<T> = [T] extends [never] ? true : false;

type A = IsNever<never>;      // true
type B = IsNever<string>;     // false

Без [T] распределение сломает проверку.

Итоги

  • T extends U ? X : Y — условный тип
  • infer извлекает тип
  • Распространение на union автоматическое
  • [T] отключает распространение
  • Основа для утилит вроде ReturnType, Awaited