JS Tower
JavaScript & TypeScriptМодуль 8: Продвинутый TypeScript

Conditional Types

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

Цель урока

В этом уроке ты научишься:

  • Создавать условные типы
  • Использовать infer
  • Применять распределение

Базовый синтаксис

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

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

Практические примеры

// Извлечение типа элемента массива
type ElementType<T> = T extends (infer E)[] ? E : never;

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

// Извлечение типа возврата
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Fn = () => string;
type R = MyReturnType<Fn>; // string

infer

Выводит тип внутри условия:

// Тип первого параметра
type FirstParam<T> = T extends (first: infer F, ...args: any[]) => any 
  ? F 
  : never;

type Fn = (name: string, age: number) => void;
type First = FirstParam<Fn>; // string

// Тип промиса
type Unpacked<T> = T extends Promise<infer U> ? U : T;

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

Распределение (Distribution)

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

type ToArray<T> = T extends any ? T[] : never;

type A = ToArray<string | number>;
// string[] | number[]

// Отключение распределения
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type B = ToArrayNonDist<string | number>;
// (string | number)[]

Встроенные условные типы

// Exclude — исключить из union
type Exclude<T, U> = T extends U ? never : T;

type A = Exclude<"a" | "b" | "c", "a">;
// "b" | "c"

// Extract — извлечь из union
type Extract<T, U> = T extends U ? T : never;

type B = Extract<"a" | "b" | "c", "a" | "d">;
// "a"

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

Сложные примеры

Глубокий Partial

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

interface User {
  name: string;
  address: {
    city: string;
    zip: string;
  };
}

type PartialUser = DeepPartial<User>;
// Все свойства опциональны, включая вложенные

Извлечение ключей по типу значения

type KeysOfType<T, V> = {
  [K in keyof T]: T[K] extends V ? K : never;
}[keyof T];

interface User {
  name: string;
  age: number;
  email: string;
}

type StringKeys = KeysOfType<User, string>;
// "name" | "email"

Практика

Задание 1: IsArray

Задача: Создай тип IsArray.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
true false

Задание 2: Unpacked

Задача: Извлеки тип из Promise или массива.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
string number

Задание 3: Exclude

Задача: Реализуй свой Exclude.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
b c

Проверь себя

  1. Что делает infer?
  2. Как работает распределение?
  3. Как отключить распределение?