Tipos Marcados en TypeScript

Cuando modelas entidades con TypeScript, es muy común obtener una interfaz como esta:

TypeScript

 

El Problema

Los tipos de las propiedades no tienen un significado semántico. En términos de tipos, User.id, Order.id, Order.year, etc. son lo mismo: un número, y como número son intercambiables, pero semánticamente, no lo son.

Siguiendo el ejemplo anterior, podemos tener un conjunto de funciones que realizan acciones sobre las entidades. Por ejemplo:

TypeScript

 

Esas funciones aceptarán cualquier número en cualquier argumento sin importar el significado semántico del número. Por ejemplo:

TypeScript

 

Obviamente, eso es un gran error, y podría parecer fácil de evitar al leer el código, pero el código no siempre es tan simple como el ejemplo.

Lo mismo ocurre con getOrdersFiltered: podemos intercambiar los valores de día y mes, y no obtendremos ninguna advertencia o error. Los errores ocurrirán si el día es mayor que 12, pero es obvio que el resultado no será el esperado.

La Solución

Las reglas del calistenia de objetos proporcionan una solución para eso: envolver todos los primitivos y cadenas (patrón anti-obsesión por primitivos relacionado). La regla es envolver los primitivos en un objeto que represente un significado semántico (DDD lo describe como ValueObjects).

Pero con TypeScript, no necesitamos usar clases u objetos para eso: podemos usar el sistema de tipos para asegurar que un número que representa algo diferente a un año no puede ser utilizado en lugar de un año.

Tipos Marcados

Este patrón utiliza la extensibilidad de los tipos para agregar una propiedad que asegure el significado semántico:

TypeScript

 

Esta línea simple crea un nuevo tipo que puede funcionar como un número, pero no es un número, es un año.

TypeScript

 

Generalizando la Solución

Para evitar escribir un tipo por cada tipo de marca, podemos crear un tipo utilitario como:

TypeScript

 

Que utiliza un símbolo único como el nombre de la propiedad de marca para evitar conflictos con tus propiedades y obtiene el tipo original y la marca como parámetros genéricos.

Con esto, podemos refactorizar nuestros modelos y funciones de la siguiente manera:

TypeScript

 

Ahora, en este ejemplo, el IDE mostrará un error ya que id es un UserId y deleteOrder espera un OrderId.

TypeScript

 

Compensaciones

Como una pequeña compensación, necesitarás usar X como Brand. Por ejemplo, const year = 2012 as Year cuando creas un nuevo valor a partir de un primitivo, pero esto es equivalente a un new Year(2012) si usas objetos de valor. Puedes proporcionar una función que funcione como una especie de “constructor”:

TypeScript

 

Validación con Tipos de Marca

Los tipos de marca también son útiles para asegurar que los datos son válidos, ya que puedes tener tipos específicos para datos validados, y puedes confiar en que el usuario fue validado solo por usar tipos:

TypeScript

 

Readonly no es obligatorio, pero para estar seguro de que tu código no cambiará los datos después de validarlos, es muy recomendable.

Resumen

Los tipos de marca son una solución simple que incluye lo siguiente:

  • Mejora la legibilidad del código: Hace más claro qué valor debe ser usado en cada argumento.
  • Confiabilidad: Ayuda a evitar errores en el código que pueden ser difíciles de detectar; ahora el IDE (y la verificación de tipos) nos ayudan a detectar si el valor está en el lugar correcto
  • Validación de datos: Puedes usar tipos etiquetados para asegurar que los datos sean válidos.

Puedes pensar en los tipos etiquetados como una especie de versión de ValueObjects pero sin usar clases — solo tipos y funciones.

¡Disfruta del poder de los tipos!

Source:
https://dzone.com/articles/branded-types-in-typescript