//
Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Usa tipos de unión discriminados para modelar props de componentes que cambian de forma basándose en un campo de variante o estado. Logra coincidencia de patrones exhaustivos para que TypeScript detecte casos faltantes en tiempo de compilación.
// Props que cambian de forma basándose en un 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}>Reintentar</button>
</div>
);
case "loading":
return <div className="alert-loading">Cargando...</div>;
}
}
// Uso
<Alert variant="success" message="¡Guardado!" />
<Alert variant="error" message="Falló" retryAction={() => refetch()} />
<Alert variant="loading" />// Ayudante de verificación exhaustiva
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${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);
// Si añades una nueva variante y olvidas manejarla,
// TypeScript lanzará un error en esta línea.
}
}AlertProps, el discriminante es variant.switch o if que verifica el discriminante, TypeScript se estrecha al miembro específico de la unión. En el caso "error", props.retryAction está disponible porque TypeScript sabe que props es { variant: "error"; message: string; retryAction: () => void }.assertNever detecta casos faltantes en tiempo de compilación. Si añades una nueva variante a la unión pero olvidas manejarla en el switch, TypeScript lanzará un error porque el nuevo tipo de variante no es asignable a never.retryAction a una alerta "success".Patrón de datos asincronos:
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>Error: {state.error.message}</div>;
}
}Unión de campo de formulario:
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 condicionales sin discriminante:
type ModalProps =
| { dismissible: true; onDismiss: () => void }
| { dismissible?: false };
// TypeScript hace que: si dismissible es true, onDismiss es requeridostring no funciona para el estrechamiento.in como alternativa: if ("retryAction" in props) se estrecha a la variante error.satisfies puede validar que un objeto coincida con una unión sin ampliar el tipo.switch (props.variant) no const { variant } = props; switch (variant) -- este último pierde la conexión entre variant y el resto de props.assertNever o habilites noFallthroughCasesInSwitch.default: return null en lugar de assertNever no detectará casos faltantes en tiempo de compilación.| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Uniones discriminadas | Los estados imposibles son inrepresentables | Definiciones de tipo más verbosas |
| Props opcionales | Definiciones de tipo más simples | Permite combinaciones de props inválidas |
| Discriminante Enum | Constantes nombradas, autocompletado de IDE | Los enums tienen sobrecarga en tiempo de ejecución, se prefieren uniones de string |
| Componentes polimórficos | Un componente, muchas formas | Firmas de tipo complejas |
| Componentes separados por variante | Cada componente es simple y enfocado | Lógica compartida duplicada |
switch o if.{ variant: "success" } | { variant: "error" }, el discriminante es variant.retryAction a una alerta "success" -- el sistema de tipos lo previene.function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}default de un switch, detecta casos faltantes en tiempo de compilación.never.string o number no funciona para el estrechamiento.const { variant } = props; switch (variant) rompe la conexión entre variant y el resto de props.switch (props.variant) para preservar el estrechamiento.type AsyncData<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };data solo es accesible cuando status es "success".if ("retryAction" in props) se estrecha al miembro de la unión que tiene retryAction.assertNever para obtener verificación de exhaustividad en tiempo de compilación.satisfies valida que un objeto coincida con una unión sin ampliar su tipo.type ModalProps =
| { dismissible: true; onDismiss: () => void }
| { dismissible?: false };onDismiss sea requerido solo cuando dismissible es true.dismissible es false u omitido, onDismiss no puede ser pasado.Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥