Discriminated unions представляют собой мощный инструмент для типизации данных и управления состоянием в приложениях на TypeScript, используемых во фреймворке Fresh. Они позволяют создавать типы, различающиеся по ключевому свойству, что облегчает обработку разнообразных структур данных без явного использования множества условных операторов или проверок типов.
Discriminated union строится на основе объединения типов, где каждый
тип имеет уникальное дискриминирующее свойство. Это
свойство, как правило, литерального типа (string или
number), которое служит «меткой», однозначно
идентифицирующей вариант типа.
Пример:
type Notification =
| { type: "email"; subject: string; body: string }
| { type: "sms"; phoneNumber: string; message: string }
| { type: "push"; title: string; content: string };
В этом примере type является дискриминирующим свойством.
Оно позволяет точно определить структуру объекта и корректно
обрабатывать данные в зависимости от типа уведомления.
Discriminated unions интегрируются с TypeScript через narrowing — сужение типа в блоках условных операторов. TypeScript автоматически распознаёт конкретный вариант union и предоставляет корректные свойства.
function handleNotification(notification: Notification) {
switch (notification.type) {
case "email":
console.log(`Email: ${notification.subject}`);
break;
case "sms":
console.log(`SMS to ${notification.phoneNumber}`);
break;
case "push":
console.log(`Push notification: ${notification.title}`);
break;
}
}
Использование switch с дискриминирующим свойством
гарантирует, что каждая ветка обрабатывает именно тот тип, для которого
она предназначена, без необходимости дополнительных проверок или кастов
типов.
В Fresh компоненты могут принимать props с discriminated unions, что позволяет гибко управлять разными визуальными и функциональными вариантами компонента.
type ButtonProps =
| { variant: "primary"; onClick: () => void }
| { variant: "secondary"; href: string };
export default function Button(props: ButtonProps) {
if (props.variant === "primary") {
return <button onCl ick={props.onClick} class="btn-primary">Primary</button>;
} else {
return <a href={props.href} class="btn-secondary">Secondary</a>;
}
}
Такой подход минимизирует ошибки, связанные с неверным использованием props, и позволяет TypeScript предупреждать о несоответствиях на этапе компиляции.
Fresh активно использует серверный рендеринг и обработку данных через
Handlers. Discriminated unions помогают безопасно
обрабатывать разные форматы запросов и ответов.
type ApiResponse =
| { status: "success"; dat a: string[] }
| { status: "error"; message: string };
export const handler: Handlers<ApiResponse> = {
async GET(req, ctx) {
const success = Math.random() > 0.5;
if (success) {
return ctx.render({ status: "success", data: ["item1", "item2"] });
} else {
return ctx.render({ status: "error", message: "Failed to fetch data" });
}
},
};
На клиенте проверка status позволяет безопасно
обрабатывать ответ без риска доступа к несуществующим полям.
function renderApiResponse(response: ApiResponse) {
if (response.status === "success") {
return response.data.join(", ");
} else {
return `Error: ${response.message}`;
}
}
Discriminated unions можно комбинировать с другими типами, включая
интерфейсы и общие (generic) типы, что делает их крайне
гибкими. Например, можно создать обобщённый тип уведомлений с
дополнительной информацией:
type BaseNotification<T extends string> = { type: T; createdAt: Date };
type ExtendedNotification =
| BaseNotification<"email"> & { subject: string; body: string }
| BaseNotification<"sms"> & { phoneNumber: string; message: string };
Такой подход облегчает поддержку и расширение кода при добавлении новых типов уведомлений или других сущностей.
Discriminated unions являются ключевым инструментом при построении безопасных и масштабируемых приложений на Fresh, обеспечивая строгую типизацию и упрощая работу с разнообразными структурами данных.