Uniones discriminantes

Uno de los problemas de las uniones de tipos es que es dificil dentro del propio código fuente que emplea una variable de tipo unión distinguir cuando la variable es de uno de los tipos concretos de la composición. Por suerte, las uniones discriminantes son una función inteligente de TypeScript para poder distinguir fácilmente entre un tipo atómico concreto dentro de una unión de tipos.

Las uniones discriminantes en TypeScript son una técnica muy útil para manejar objetos que pueden tener diferentes formas, pero que comparten un campo común que nos permite distinguir entre ellos. Este campo, que tiene el mismo nombre en todos los tipos que forman la unión, contiene un valor literal único para cada subtipo, lo que nos ayuda a identificar con seguridad qué tipo de objeto estamos tratando en cada momento.

Para entenderlo mejor, imaginemos que definimos distintos tipos para representar operaciones matemáticas. Por ejemplo, una operación suma podría ser un objeto con dos propiedades, sumando1 y sumando2, y un campo especial llamado tipo cuyo valor literal es "suma". Esto significa que para que un objeto sea considerado una operación suma, su propiedad tipo debe tener exactamente el valor "suma". De manera similar, podemos definir una operación multiplicar con propiedades operando1 y operando2, y un campo tipo con el valor literal "multiplicar".

Con estos tipos definidos, podemos crear una unión llamada Operacion que sea la combinación de OperacionSuma y OperacionMultiplicar. Así, una variable de tipo Operacion puede contener cualquiera de estas dos formas.

Cuando escribimos una función que recibe una Operacion, por ejemplo operar, nos encontramos con que TypeScript solo nos ofrece acceso a las propiedades comunes a todos los tipos de la unión, como el campo tipo. Sin embargo, gracias a que tipo es un literal que solo puede ser "suma" o "multiplicar", podemos usarlo como una guarda para diferenciar qué tipo de operación tenemos en cada caso.

Por ejemplo, si dentro de la función comprobamos que o.tipo === "suma", TypeScript infiere automáticamente que o es de tipo OperacionSuma. Esto nos permite acceder sin problemas a las propiedades sumando1 y sumando2. De forma análoga, si o.tipo === "multiplicar", TypeScript sabe que o es de tipo OperacionMultiplicar y nos deja trabajar con operando1 y operando2.

Este patrón es especialmente valioso porque nos permite escribir código seguro y claro, evitando errores al acceder a propiedades que no existen en ciertos tipos. Además, es un enfoque que se utiliza mucho en librerías populares. Por ejemplo, Redux usa un campo type en sus acciones para distinguir entre diferentes tipos de acciones, y Discord.js emplea un sistema similar para diferenciar entre mensajes enviados a canales, grupos de DM o mensajes directos a una persona.

En definitiva, las uniones discriminantes nos permiten construir jerarquías de tipos complejas y trabajar con ellas de manera sencilla y segura, aprovechando el poder del sistema de tipos de TypeScript para discriminar entre variantes de objetos según un valor literal común. Esto hace que sea una herramienta fundamental para cualquier desarrollador que quiera manejar tipos de forma avanzada y mantener un código robusto y mantenible.

Para ilustrar este concepto, podemos definir los tipos y la función operar de la siguiente manera:

type OperacionSuma = {
  tipo: "suma";
  sumando1: number;
  sumando2: number;
};

type OperacionMultiplicar = {
  tipo: "multiplicar";
  operando1: number;
  operando2: number;
};

type Operacion = OperacionSuma | OperacionMultiplicar;

function operar(o: Operacion): number {
  if (o.tipo === "suma") {
    return o.sumando1 + o.sumando2;
  } else if (o.tipo === "multiplicar") {
    return o.operando1 * o.operando2;
  }
  // Opcionalmente, podríamos manejar un caso por defecto o lanzar un error
}

Con este código, podemos pasar a la función operar cualquier objeto que sea una suma o una multiplicación, y gracias a la unión discriminante, TypeScript nos ayuda a trabajar con cada tipo de forma segura y con autocompletado, mejorando la experiencia de desarrollo y la calidad del código.

Lista de reproducción
  1. 1
    Temporada 1
    5 minutos
  2. 2
    ¿Qué es TypeScript?
    11 minutos
  3. 3
    Instalando TypeScript
    8 minutos
  4. 4
    Compilando un Hola Mundo sencillo
    7 minutos
  5. 5
    Hola Mundo pero con tipos
    10 minutos
  6. 6
    Tipos: tipos primitivos
    12 minutos
  7. 7
    Tipos: tipos especiales (any, null, ...)
    10 minutos
  8. 8
    Tipos: arrays y tuplas
    11 minutos
  9. 9
    Tipos: objetos
    7 minutos
  10. 10
    Funciones: lo básico
    9 minutos
  11. 11
    Funciones: tipando funciones
    9 minutos
  12. 12
    Clases: introducción a las clases
    9 minutos
  13. 13
    Clases: creando una clase
    10 minutos
  14. 14
    Clases: modificador private
    8 minutos
  15. 15
    Clases: modificador readonly
    3 minutos
  16. 16
    Clases: Atributos virtuales con getters y setters
    10 minutos
  17. 17
    Clases: herencia
    9 minutos
  18. 18
    Clases: modificadores abstract y protected
    8 minutos
  19. 19
    Tipos alias
    6 minutos
  20. 20
    Tipos literales
    5 minutos
  21. 21
    Uniones de tipos
    7 minutos
  22. 22
    Uniones discriminantes
    7 minutos
  23. 23
    Intersecciones de tipos
    5 minutos
  24. 24
    Interfaces: introducción
    7 minutos
  25. 25
    Interfaces: modificadores y funciones
    9 minutos
  26. 26
    Interfaces: usándolas con clases
    8 minutos
  27. 27
    Interfaces: herencia de interfaces
    8 minutos
  28. 28
    Interfaces: interfaces indizadas
    5 minutos
  29. 29
    Interfaces: funciones y tipos híbridos
    5 minutos
  30. 30
    ¿Qué diferencia hay entre interfaces y tipos? (2020)
    8 minutos
  31. 31
    Casteos con as
    6 minutos
  32. 32
    instanceof y las guardas
    9 minutos
  33. 33
    Tipos enumerados
    8 minutos
  34. 34
    Valores avanzados para enumerados
    7 minutos
  35. 35
    Enumerados con valores computados
    6 minutos
  36. 36
    Genéricos en tipos
    8 minutos
  37. 37
    Múltiples genéricos y buenas prácticas
    5 minutos
  38. 38
    Genéricos en funciones
    8 minutos
  39. 39
    Genéricos con restricciones
    6 minutos
  40. 40
    Tipos de utilidad
    3 minutos
  41. 41
    Exportando módulos
    9 minutos
  42. 42
    Importando módulos
    7 minutos
  43. 43
    Export default e import asterisco
    6 minutos
  44. 44
    tsconfig
    7 minutos
  45. 45
    Módulos desde NPM
    7 minutos
  46. 46
    Arroba types y los .d.ts
    8 minutos
  47. 47
    Ejemplo (1): creando una API REST simple en TypeScript
    11 minutos
  48. 48
    Ejemplo (2): montando un servidor Express
    8 minutos
  49. 49
    Ejemplo (3): haciendo las funciones de control de datos
    11 minutos
  50. 50
    Ejemplo (4): conectando todas las piezas
    7 minutos