Intersection Types

Intersection Types (A & B) — тип, объединяющий свойства нескольких типов: значение должно удовлетворять всем типам одновременно, то есть содержать все поля каждого из них.

Зачем нужно

Intersection типы позволяют комбинировать независимые типы без наследования. Это основной механизм создания «составных» типов из отдельных частей (mixins, merge объектов, аугментация типов).

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

  • Mixins и композиция поведения
  • Объединение базового типа с дополнительными свойствами (WithId, WithTimestamps)
  • Функции, принимающие объекты с несколькими контрактами
  • Branded Types (string & { _brand: "..." })
  • Тип результата Object.assign и spread-операций

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

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

type Named  = { name: string };
type Aged   = { age: number };
type Person = Named & Aged;

const alice: Person = { name: "Alice", age: 30 }; // OK
const bob: Person   = { name: "Bob" };             // Error — нет age

Паттерн: добавление мета-полей

type WithId<T> = T & { id: string };
type WithTimestamps<T> = T & { createdAt: Date; updatedAt: Date };

interface UserInput {
  name: string;
  email: string;
}

type User = WithId<WithTimestamps<UserInput>>;
// { name: string; email: string; id: string; createdAt: Date; updatedAt: Date }

function createUser(input: UserInput): User {
  return {
    ...input,
    id: crypto.randomUUID,
    createdAt: new Date,
    updatedAt: new Date,
  };
}

Intersection примитивов — never

type Impossible = string & number; // never
// Применяется в Branded Types:
type UserId = string & { readonly _brand: "UserId" };

Intersection vs extends интерфейсов

// При конфликте свойств:
interface A { value: string }
interface B { value: number }
// interface C extends A, B {} // Error — конфликт типа value

type C = A & B; // OK синтаксически, но value: string & number = never

Generic merge-функция

function merge<T, U>(a: T, b: U): T & U {
  return { ...a, ...b } as T & U;
}

const merged = merge({ name: "Alice" }, { age: 30 });
// { name: string; age: number }
console.log(merged.name); // OK
console.log(merged.age);  // OK

Intersection интерфейсов-контрактов

interface Serializable {
  serialize: string;
}

interface Persistable {
  save: Promise<void>;
}

type StorableEntity = Serializable & Persistable;

class Order implements Serializable, Persistable {
  serialize { return JSON.stringify(this); }
  async save { /* ... */ }
}

const order: StorableEntity = new Order();

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

  • Путать & и |& требует всего из обоих типов (intersection), | принимает любой из них (union).
  • Intersection примитивов даёт neverstring & number = never; используйте | для «одно из».
  • Конфликт полей при intersection(A & B).field где типы поля несовместимы = never для этого поля.
  • Мутировать merged-объектas T & U это cast, не глубокое копирование; побочные эффекты остаются.

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

Ресурсы