Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
TypeScript Union в интерфейсах: правильный синтаксис
Отличный вопрос о работе с Union types в TypeScript интерфейсах.
Базовый синтаксис Union
Union тип (объединение) описывает значение, которое может быть ОДНОГО из нескольких типов:
// Union из примитивов
type Status = 'loading' | 'success' | 'error';
// Union из типов
type Result = string | number | boolean;
// Union из объектов
type Response = SuccessResponse | ErrorResponse;
Union в интерфейсах
Способ 1: Поле с Union типом
interface User {
id: string;
name: string;
status: 'active' | 'inactive' | 'banned';
age: number | null; // number или null
permissions: string[];
}
Способ 2: Union из интерфейсов
interface SuccessResponse {
status: 'success';
data: unknown;
code: 200;
}
interface ErrorResponse {
status: 'error';
message: string;
code: 400 | 404 | 500;
}
type ApiResponse = SuccessResponse | ErrorResponse;
Теперь ApiResponse может быть либо успешный, либо ошибочный ответ.
Discriminated Union (Дискриминированное объединение)
Это паттерн, где одно поле (discriminator) определяет тип:
interface LoadingState {
status: 'loading';
progress: number;
}
interface SuccessState {
status: 'success';
data: any;
}
interface ErrorState {
status: 'error';
error: Error;
}
type AsyncState = LoadingState | SuccessState | ErrorState;
// Использование
function handleState(state: AsyncState) {
if (state.status === 'loading') {
console.log('Загруз:', state.progress); // ТС знает про progress
} else if (state.status === 'success') {
console.log('Успех:', state.data); // ТС знает про data
} else if (state.status === 'error') {
console.log('Ошибка:', state.error); // ТС знает про error
}
}
Это мощнее обычного Union, потому что TypeScript может сузить тип по discriminator.
Union в параметрах компонента
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size: 'sm' | 'md' | 'lg';
onClick: () => void;
}
interface LinkProps {
href: string;
target: '_blank' | '_self';
external: boolean;
}
// Компонент может быть или кнопка, или ссылка
type ActionProps = ButtonProps | LinkProps;
// Но это неудобно, нужен discriminator
type ActionProps =
| (ButtonProps & { type: 'button' })
| (LinkProps & { type: 'link' });
Практические примеры
1. Платежная система
interface CreditCardPayment {
method: 'credit_card';
cardNumber: string;
cvv: string;
expiryDate: string;
}
interface PayPalPayment {
method: 'paypal';
email: string;
token: string;
}
interface CryptoPayment {
method: 'crypto';
walletAddress: string;
coinType: 'bitcoin' | 'ethereum';
}
type PaymentMethod = CreditCardPayment | PayPalPayment | CryptoPayment;
function processPayment(payment: PaymentMethod) {
switch (payment.method) {
case 'credit_card':
console.log('Обработка карты:', payment.cardNumber);
break;
case 'paypal':
console.log('PayPal:', payment.email);
break;
case 'crypto':
console.log('Крипто:', payment.coinType);
break;
}
}
2. Форма с разными типами полей
interface TextField {
type: 'text';
label: string;
placeholder: string;
maxLength: number;
}
interface SelectField {
type: 'select';
label: string;
options: { value: string; label: string }[];
multiple: boolean;
}
interface CheckboxField {
type: 'checkbox';
label: string;
checked: boolean;
}
type FormField = TextField | SelectField | CheckboxField;
function renderField(field: FormField) {
switch (field.type) {
case 'text':
return <input placeholder={field.placeholder} maxLength={field.maxLength} />;
case 'select':
return <select multiple={field.multiple}>{field.options.map(...)}</select>;
case 'checkbox':
return <input type="checkbox" checked={field.checked} />;
}
}
3. Асинхронное состояние
type LoadingState = {
state: 'loading';
};
type SuccessState<T> = {
state: 'success';
data: T;
};
type ErrorState = {
state: 'error';
error: string;
};
type AsyncResult<T> = LoadingState | SuccessState<T> | ErrorState;
// Использование
function useAsync<T>(fn: () => Promise<T>): AsyncResult<T> {
const [result, setResult] = useState<AsyncResult<T>>({ state: 'loading' });
useEffect(() => {
fn()
.then(data => setResult({ state: 'success', data }))
.catch(error => setResult({ state: 'error', error: error.message }));
}, [fn]);
return result;
}
Сужение типа (Type Narrowing)
ТипScript умеет сужать Union автоматически:
type Value = string | number;
function process(val: Value) {
// val: string | number
if (typeof val === 'string') {
// val: string
console.log(val.toUpperCase());
} else {
// val: number
console.log(val.toFixed(2));
}
}
Union с Generics
type Either<L, R> =
| { type: 'left'; value: L }
| { type: 'right'; value: R };
const success: Either<Error, number> = { type: 'right', value: 42 };
const error: Either<Error, number> = { type: 'left', value: new Error('Fail') };
function extract<L, R>(either: Either<L, R>): L | R {
if (either.type === 'left') return either.value;
return either.value;
}
Частые ошибки
// Ошибка 1: Забыли discriminator
type Bad =
| { id: string; name: string }
| { id: string; email: string };
// Непонятно какой тип, сложно работать
// Правильно:
type Good =
| { type: 'user'; id: string; name: string }
| { type: 'admin'; id: string; email: string };
// Ошибка 2: Слишком широкий Union
type Bad = string | number | boolean | object | null | undefined;
// Лучше использовать unknown с type guards
// Ошибка 3: Union где нужен Array
type Bad = string | string | string; // Лучше string[]
Вывод
Union типы в интерфейсах:
- Для простых значений:
status: 'loading' | 'success' | 'error' - Для сложных: Discriminated Union с уникальным полем
- С Generics: для гибкости, как Either<L, R>
- Всегда используй type guards для сужения типов
Discriminated Union - это gold standard в TypeScript, используй его везде где есть Union из нескольких интерфейсов.