Exclude, Extract, NonNullable

Exclude, Extract и NonNullable — утилитарные типы TypeScript для фильтрации union-типов: Exclude убирает варианты, Extract оставляет только совпадающие, NonNullable удаляет null и undefined.

Зачем нужно

При работе с union-типами часто нужно получить подмножество вариантов — например, только события определённого типа или строки без null. Эти утилиты позволяют делать это декларативно на уровне типов, без дублирования деклараций.

Где используется

  • Exclude — убрать из union конкретные варианты (например, убрать null и undefined вручную)
  • Extract — извлечь только совпадающие с шаблоном варианты (например, все строки из string | number | boolean)
  • NonNullable — убрать null | undefined из любого типа (обёртка над Exclude)
  • Типизация discriminated unions — выбрать только нужные события из общего union

Основной контент

Exclude<T, U>

Убирает из T все варианты, присваиваемые к U.

type T1 = Exclude<string | number | boolean, boolean>;
// string | number

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

type T3 = Exclude<string | null | undefined, null | undefined>;
// string — аналогично NonNullable

// Реализация внутри TS:
// type Exclude<T, U> = T extends U ? never : T;

Extract<T, U>

Оставляет в T только варианты, присваиваемые к U.

type T1 = Extract<string | number | boolean, string | number>;
// string | number

type T2 = Extract<"circle" | "square" | "triangle", "circle" | "square">;
// "circle" | "square"

// Реализация:
// type Extract<T, U> = T extends U ? T : never;

NonNullable<T>

Убирает null и undefined из типа.

type T1 = NonNullable<string | null | undefined>;
// string

type T2 = NonNullable<number | null>;
// number

type T3 = NonNullable<null | undefined>;
// never

// Реализация:
// type NonNullable<T> = T & {};

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

// Извлечение конкретных action-типов из union
type Action =
  | { type: "FETCH_USER"; id: string }
  | { type: "FETCH_ORDER"; id: string }
  | { type: "RESET" };

type FetchActions = Extract<Action, { type: `FETCH_${string}` }>;
// { type: "FETCH_USER"; id: string } | { type: "FETCH_ORDER"; id: string }

// Убрать RESET из Action
type NonResetAction = Exclude<Action, { type: "RESET" }>;
// { type: "FETCH_USER"; ... } | { type: "FETCH_ORDER"; ... }

// NonNullable для строгой типизации
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // User

// В generic функции
function compact<T>(arr: (T | null | undefined)): NonNullable<T> {
  return arr.filter((x): x is NonNullable<T> => x != null);
}

Комбинирование

// Все ключи объекта кроме функций
type NonFunctionKeys<T> = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

// Только string-ключи из union
type StringValues = Extract<string | number | boolean | object, string>;
// string

Частые ошибки

  • Путать Exclude и Extract — Exclude убирает, Extract оставляет.
  • Применять Exclude к объектным типамExclude<{ a: 1 }, { a: 1 }> не уберёт объект (структурная совместимость); лучше использовать с literal union.
  • Думать что NonNullable = Exclude<T, null> — NonNullable убирает и null, и undefined одновременно.
  • Использовать NonNullable вместо ! оператора — NonNullable работает на уровне типов, ! — в рантайме.

Связанные темы

Ресурсы