//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Use tipos de união discriminados para modelar props de componentes que mudam de forma com base em um campo de variante ou status. Obtenha correspondência de padrões exaustiva para que o TypeScript detecte casos ausentes em tempo de compilação.
// Props que mudam de forma com base em um discriminante "variant"
type AlertProps =
| { variant: "success"; message: string }
| { variant: "error"; message: string; retryAction: () => void }
| { variant: "loading" };
function Alert(props: AlertProps) {
switch (props.variant) {
case "success":
return <div className="alert-success">{props.message}</div>;
case "error":
return (
<div className="alert-error">
<p>{props.message}</p>
<button onClick={props.retryAction}>Tentar Novamente</button>
</div>
);
case "loading":
return <div className="alert-loading">Carregando...</div>;
}
}
// Uso
<Alert variant="success" message="Salvo!" />
<Alert variant="error" message="Falhou" retryAction={() => refetch()} />
<Alert variant="loading" />// Helper de verificação exaustiva
function assertNever(value: never): never {
throw new Error(`Valor inesperado: ${value}`);
}
function getStatusColor(props: AlertProps): string {
switch (props.variant) {
case "success": return "green";
case "error": return "red";
case "loading": return "gray";
default: return assertNever(props);
// Se você adicionar uma nova variante e esquecer de tratá-la,
// o TypeScript dará erro nesta linha.
}
}AlertProps, o discriminante é variant.switch ou if que verifica o discriminante, o TypeScript estreita o tipo para o membro específico da união. No caso "error", props.retryAction está disponível porque o TypeScript sabe que props é { variant: "error"; message: string; retryAction: () => void }.assertNever captura casos ausentes em tempo de compilação. Se você adicionar uma nova variante à união, mas esquecer de tratá-la no switch, o TypeScript dará erro porque a nova variante de tipo não é atribuível a never.retryAction para um alerta "success".Padrão de dados assíncronos:
type AsyncData<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
function UserProfile({ state }: { state: AsyncData<User> }) {
switch (state.status) {
case "idle":
return null;
case "loading":
return <Spinner />;
case "success":
return <div>{state.data.name}</div>;
case "error":
return <div>Erro: {state.error.message}</div>;
}
}União de campo de formulário:
type FormField =
| { type: "text"; label: string; placeholder?: string }
| { type: "select"; label: string; options: string[] }
| { type: "checkbox"; label: string; checked: boolean };
function FormFieldComponent({ field }: { field: FormField }) {
switch (field.type) {
case "text":
return <input type="text" placeholder={field.placeholder} />;
case "select":
return (
<select>
{field.options.map((opt) => <option key={opt}>{opt}</option>)}
</select>
);
case "checkbox":
return <input type="checkbox" defaultChecked={field.checked} />;
}
}Props condicionais sem um discriminante:
type ModalProps =
| { dismissible: true; onDismiss: () => void }
| { dismissible?: false };
// O TypeScript garante: se dismissible for true, onDismiss é obrigatóriostring não funciona para estreitamento.in como alternativa: if ("retryAction" in props) estreita para o membro da união que possui retryAction.satisfies pode validar que um objeto corresponde a uma união sem alargar o tipo.switch (props.variant) em vez de const { variant } = props; switch (variant) -- o último perde a conexão entre variant e o resto de props.assertNever ou habilite noFallthroughCasesInSwitch.default: return null em vez de assertNever não detectará casos ausentes em tempo de compilação.| Abordagem | Prós | Contras |
|---|---|---|
| Uniões discriminadas | Estados impossíveis são irrepresentáveis | Definições de tipo mais verbosas |
| Props opcionais | Definições de tipo mais simples | Permite combinações de props inválidas |
| Discriminante de enum | Constantes nomeadas, autocompletar IDE | Enums têm sobrecarga de tempo de execução, uniões de string são preferidas |
| Componentes polimórficos | Componente único, muitas formas | Assinaturas de tipo complexas |
| Componentes separados por variante | Cada componente é simples e focado | Duplicação de lógica compartilhada |
switch ou if.{ variant: "success" } | { variant: "error" }, o discriminante é variant.retryAction para um alerta "success" -- o sistema de tipos impede isso.function assertNever(value: never): never {
throw new Error(`Valor inesperado: ${value}`);
}default de um switch, ele captura casos ausentes em tempo de compilação.never.string ou number simples não funciona para estreitamento.const { variant } = props; switch (variant) quebra a conexão entre variant e o resto de props.props para o membro correto da união.switch (props.variant) para preservar o estreitamento.type AsyncData<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };data só é acessível quando status é "success".if ("retryAction" in props) estreita para o membro da união que possui retryAction.assertNever para obter verificação de exaustividade em tempo de compilação.satisfies valida que um objeto corresponde a uma união sem alargar seu tipo.type ModalProps =
| { dismissible: true; onDismiss: () => void }
| { dismissible?: false };onDismiss é obrigatório apenas quando dismissible é true.dismissible é false ou omitido, onDismiss não pode ser passado.Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥