← Назад к вопросам

Как объединить два типа в TypeScript?

1.7 Middle🔥 191 комментариев
#JavaScript Core#TypeScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Объединение двух типов в TypeScript

TypeScript предоставляет несколько способов объединения типов для создания новых типов с комбинированием свойств. Это называется composition (композиция типов). Рассмотрю основные способы: intersection types, union types, extends и другие методы объединения.

Способ 1: Intersection (&) — объединение свойств

Пересечение типов объединяет свойства обоих типов:

type Admin = {
  name: string;
  adminLevel: number;
};

type User = {
  email: string;
  isActive: boolean;
};

// Пересечение: объединяет ВСЕ свойства обоих типов
type AdminUser = Admin & User;

const adminUser: AdminUser = {
  name: 'John',
  adminLevel: 5,
  email: 'john@example.com',
  isActive: true
};

Практический пример:

type HasId = {
  id: string;
};

type HasTimestamp = {
  createdAt: Date;
  updatedAt: Date;
};

// Комбинируем типы
type Entity = HasId & HasTimestamp;

const entity: Entity = {
  id: 'uuid-123',
  createdAt: new Date(),
  updatedAt: new Date()
};

Способ 2: Union (|) — выбор одного из типов

Объединение типов (union) означает, что значение может быть одним из нескольких типов:

type Admin = {
  role: 'admin';
  adminLevel: number;
};

type User = {
  role: 'user';
  email: string;
};

// Union: может быть Admin ИЛИ User
type Account = Admin | User;

const admin: Account = {
  role: 'admin',
  adminLevel: 10
};

const user: Account = {
  role: 'user',
  email: 'user@example.com'
};

// Проблема: не все свойства доступны на все типы
// account.adminLevel // Ошибка: может быть User без adminLevel

Способ 3: Наследование (extends) для интерфейсов

Интерфейсы можно расширять, наследуя свойства:

interface Base {
  id: string;
  name: string;
}

interface Extended extends Base {
  description: string;
  tags: string[];
}

const item: Extended = {
  id: '1',
  name: 'Product',
  description: 'A nice product',
  tags: ['electronics']
};

Множественное наследование:

interface Identifiable {
  id: string;
}

interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface Viewable {
  views: number;
}

// Наследуем несколько интерфейсов
interface Post extends Identifiable, Timestamped, Viewable {
  title: string;
  content: string;
}

const post: Post = {
  id: 'post-1',
  createdAt: new Date(),
  updatedAt: new Date(),
  views: 100,
  title: 'My Post',
  content: 'Post content'
};

Способ 4: Mapped Types для трансформации

Mapped types позволяют создавать новые типы на основе существующих:

type ReadOnly<T> = {
  readonly [K in keyof T]: T[K];
};

type User = {
  name: string;
  email: string;
};

// Преобразуем все свойства в read-only
type ReadOnlyUser = ReadOnly<User>;

const user: ReadOnlyUser = { name: 'John', email: 'john@example.com' };
// user.name = 'Jane'; // Ошибка: cannot assign to readonly property

Объединение двух типов с mapped types:

type Merge<T, U> = {
  [K in keyof T]: T[K];
} & {
  [K in keyof U]: U[K];
};

type Admin = { role: 'admin'; level: number };
type User = { name: string; email: string };

type MergedUser = Merge<Admin, User>;

const user: MergedUser = {
  role: 'admin',
  level: 5,
  name: 'John',
  email: 'john@example.com'
};

Способ 5: Conditional Types для условного объединения

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

type Admin = { role: 'admin' };
type User = { email: string };

// Выбираем тип в зависимости от условия
type GetUserType<T extends 'admin' | 'user'> = 
  T extends 'admin' 
    ? Admin 
    : User;

const admin: GetUserType<'admin'> = { role: 'admin' };
const user: GetUserType<'user'> = { email: 'user@example.com' };

Способ 6: Tuple Types для комбинирования

type Tuple<T, U> = [T, U];

type StringNumber = Tuple<string, number>;

const pair: StringNumber = ['hello', 42];

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

Пример 1: API Response с разными типами данных

type SuccessResponse<T> = {
  status: 'success';
  data: T;
};

type ErrorResponse = {
  status: 'error';
  message: string;
};

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

function handleResponse<T>(response: ApiResponse<T>) {
  if (response.status === 'success') {
    console.log(response.data);
  } else {
    console.log(response.message);
  }
}

Пример 2: Компонент с разными props

type ButtonProps = {
  size: 'small' | 'medium' | 'large';
  onClick: () => void;
};

type LinkProps = {
  href: string;
  target?: '_blank' | '_self';
};

type CommonProps = {
  children: React.ReactNode;
  className?: string;
};

// Кнопка-ссылка
type ButtonLinkProps = ButtonProps & LinkProps & CommonProps;

interface Props extends ButtonLinkProps {
  rel?: string;
}

const ButtonLink: React.FC<Props> = ({ size, onClick, href, children }) => {
  return (
    <a href={href} onClick={onClick} className={`btn-${size}`}>
      {children}
    </a>
  );
};

Пример 3: Объединение с Required/Partial

type User = {
  id: string;
  name: string;
  email?: string;
};

// Делаем все свойства обязательными
type FullUser = Required<User>;

// Делаем все свойства необязательными
type PartialUser = Partial<User>;

// Выбираем только нужные свойства
type UserPreview = Pick<User, 'id' | 'name'>;

// Исключаем свойства
type UserWithoutId = Omit<User, 'id'>;

Сравнение подходов

СпособИспользованиеРезультат
& (Intersection)Объединить все свойстваВсе свойства обоих типов
| (Union)Выбрать один типОдин из типов
extendsНаследованиеРасширенный интерфейс
Mapped TypesТрансформацияНовый тип на основе старого

Выборка между Intersection и Union

Intersection (&):

  • Используй когда объект должен ОДНОВРЕМЕННО удовлетворять обоим типам
  • Пример: пользователь может быть и админом, и активным одновременно
type Admin = { role: 'admin' };
type Active = { isActive: true };
type AdminUser = Admin & Active; // Объект должен быть обоими

Union (|):

  • Используй когда значение может быть ОДНИМ из типов
  • Пример: результат может быть успехом или ошибкой
type Result<T> = 
  | { success: true; data: T }
  | { success: false; error: string };

Сложный пример: комбинирование нескольких техник

// Базовые типы
type Identifiable = { id: string };
type Timestamped = { createdAt: Date; updatedAt: Date };

// Параметризованный тип
type Paginated<T> = {
  items: T[];
  total: number;
  page: number;
};

// Union для разных статусов
type EntityStatus = 'active' | 'archived' | 'deleted';

// Комбинируем всё вместе
type Post = Identifiable & Timestamped & {
  title: string;
  content: string;
  status: EntityStatus;
};

type PaginatedPosts = Paginated<Post>;

const response: PaginatedPosts = {
  items: [
    {
      id: '1',
      title: 'First Post',
      content: 'Content...',
      status: 'active',
      createdAt: new Date(),
      updatedAt: new Date()
    }
  ],
  total: 100,
  page: 1
};

Выводы

  • Используй & (intersection) для объединения свойств обоих типов
  • Используй | (union) когда значение может быть одним из нескольких типов
  • Наследование (extends) лучше для интерфейсов с иерархией
  • Mapped Types для трансформации и создания нового типа на основе старого
  • Комбинируй техники для создания сложных типов
  • Выбирай между intersection и union в зависимости от логики приложения