Kodokon kodokon.com

Tipos literales y uniones discriminadas

Modela los estados de tu aplicación con uniones que el compilador comprueba por ti.

8 min · 3 preguntas

Abrir esta lección en Kodokon

Un tipo literal restringe un valor a un contenido exacto: 'info' en lugar de string. Es la base de las API imposibles de usar mal: el compilador rechaza cualquier valor fuera de la lista y tu editor ofrece autocompletado.

TYPESCRIPT
type Level = "info" | "warn" | "error";

function log(level: Level, message: string): void {
  console.log(`[${level}] ${message}`);
}

log("info", "Server started");
// log("fatal", "Boom"); -> rejected by the compiler
Una unión de literales bloquea los valores permitidos

El siguiente paso es combinar tipos de objeto que comparten una propiedad literal común, llamada el discriminante. Cada estado de una llamada de red, por ejemplo, lleva solo los datos que le corresponden: acceder a data durante la carga es sencillamente imposible.

TYPESCRIPT
type ApiState =
  | { status: "loading" }
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

function render(state: ApiState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return state.data.join(", ");
    case "error":
      return state.message;
  }
}
Una unión discriminada por la propiedad status

En cada case, TypeScript acota el tipo: después de case 'error', state.message existe y state.data no. Este mecanismo, llamado narrowing (acotamiento), reemplaza las comprobaciones manuales frágiles. Añade una guardia de exhaustividad para blindarlo todo.

TYPESCRIPT
function assertNever(value: never): never {
  throw new Error("Unhandled case: " + String(value));
}

function label(state: ApiState): string {
  switch (state.status) {
    case "loading": return "In progress";
    case "success": return "Done";
    case "error": return "Failed";
    default: return assertNever(state);
  }
}
Guardia de exhaustividad con el tipo never

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. En una unión discriminada, ¿cuál es el papel de la propiedad compartida como status?
    • Mejorar el rendimiento en tiempo de ejecución
    • Permitir que el compilador acote el tipo en cada rama
    • Hacer la propiedad obligatoria en el JSON recibido
    • Evitar propiedades duplicadas
  2. Con type Level = 'info' | 'warn', ¿qué llamada a log(level, msg) compila?
    • log('debug', msg)
    • log('info', msg)
    • log(someString, msg) donde someString es de tipo string
  3. ¿Cuál es el propósito de una función assertNever(value: never) en la rama default de un switch?
    • Registrar errores en producción
    • Convertir un caso olvidado en un error de compilación
    • Convertir el valor a never
    • Terminar el programa más rápido