Patrones
¿Cómo obtengo un error de compilación cuando alguien añade un caso a una unión y olvida gestionarlo?
Acota cada caso y asigna lo que quede a `never`. Cuando la unión crece, lo que queda deja de ser nada y la asignación falla.
La receta
type Shape = | { kind: 'circle'; r: number } | { kind: 'square'; side: number } function assertNever(value: never): never { throw new Error(`Unhandled: ${JSON.stringify(value)}`) } function area(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.r ** 2 case 'square': return shape.side ** 2 default: return assertNever(shape) } }
La compilación compila esto y comprueba cada resultado de abajo.
Cómo funciona
- 01
function assertNever(value: never): never {
Un parámetro de tipo `never` no acepta nada. El único valor que puedes pasar es uno que el compilador ya ha demostrado imposible.
- 02
return assertNever(shape)
En la rama por defecto ya se ha retornado desde cada caso conocido, así que `shape` se ha acotado a `never` y esto compila. Añade una tercera forma y deja de compilar — en esta línea, nombrando el tipo que olvidaste.
- 03
throw new Error
Lanza en vez de retornar, porque un valor que se suponía imposible llegando en tiempo de ejecución es un fallo, no un caso que absorber.
Lo que obtienes
ReturnType<typeof area>→numberShape['kind']→"circle" | "square"Extract<Shape, { kind: 'circle' }>→{ kind: "circle"; r: number; }El discriminante es lo que hace posible el acotado — una propiedad literal que todos los miembros tienen y que ninguno comparte.
Dónde falla
Solo funciona si cada caso retorna o lanza. Un `break` que cae al default deja `shape` sin acotar, y la comprobación deja de comprobar nada en silencio.
Conclusión
Es el patrón de mayor valor del lenguaje. Convierte «olvidamos gestionar el caso nuevo» de incidente en producción a error de compilación.