Как объединить два типа в TypeScript?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Объединение двух типов в 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 в зависимости от логики приложения