Uno de los puntos fuertes de Typescript, es la flexibilidad que nos brinda en el momento de definir estructuras de datos.
Esta misma flexibilidad puede ser, al mismo tiempo, la mayor fuente de problemas en las manos inadecuadas, como podremos ver a continuación.
Hace algunas semanas definimos en nuestra aplicación un tipo llamado UserAddress
:
export interface UserAddress {
firstName: string
middleName: string
lastName: string
email: string
address: Address
}
Tal y como se muestra, esta interfaz define las propiedades referentes a un usuario y la dirección del mismo.
Todo iba bien, hasta que comenzaron a surgir requisitos por parte de la aplicación, donde la dirección de un usuario podía ser una dirección para Billing
o para Shipping
. ¿Qué implicaciones tiene esto? A priori ninguna.
Sin embargo, pocos días después fuimos notificados de que a su vez, Shipping
podía ser tanto físico como digital.
Al par de días haciendo una CR vimos algo extraño: estábamos teniendo un error en la aplicación, ya que se daban casos muy extraños donde se trataba de una dirección de shipping digital, pero estaban presentes los datos de shipping físicos.
Al revisar el código nos encontramos lo siguiente:
export interface UserAddress {
firstName: string
middleName?: string
lastName: string
email?: string
activationEmail?: string
address?: Address
}
Ahora bien, como solución es totalmente válida, sin embargo, el tratar de utilizar un mismo tipo de forma genérica, y marcando sus propiedades como opcionales, puede incurrir en muchos errores como era el caso.
¿Cómo lo podemos solucionar? Siendo más específicos. Partamos de una base: ¿Qué tipos son esenciales para definir la dirección de un usuario? Pues en primer lugar, los datos del usuario (en este caso Customer
):
export type Customer = {
firstName: string
middleName?: string
lastName: string
email: string
}
¿Qué caracteriza a una dirección digital? Se trata de un Customer
, pero en lugar de tener un campo email
, posee un campo activationEmail
. Esto puede ser definido de la siguiente manera:
export type UserDigitalAddress = Omit<Customer, 'email'> & { activationEmail: string }
Y por su parte, una dirección física se caracteriza por tener la información de la calle, número, país (envuelto en el tipo Address
):
export type UserPhysicalAddress = Customer & { address: Address }
Por último, se nos da el caso de que una misma compra, puede ser al mismo tiempo: física, si el envío se tiene que enviar a un domicilio; o digital, si únicamente se requiere activar en una cuenta. De ser así, podemos afirmar lo siguiente:
export type UserDigitalPhysicalAddress = UserDigitalAddress & UserPhysicalAddress
Teniendo todos estos tipos, ahora podremos agruparlo en uno, el cual conocerá las restricciones de cada uno, y admitirá que se use cualquiera de sus definiciones, siempre que cumpla la definición de una de ellas:
export type UserShippingAddress = UserDigitalAddress|UserPhysycalAddress|UserDigitalShippingAddress
Por consiguiente, podemos decir que tanto la dirección de Billing
y de Shipping
cumplen la siguiente definición:
export type UserShippingAddress = UserDigitalAddress|UserPhysycalAddress|UserDigitalShippingAddress
export type UserBillingAddress = UserPhysicalAddress
Pudiendo ser usada de la siguiente manera:
const checkout = (billing: UserBillingAddress, shipping: UserShippingAddress , customer: Customer) => {
// ...
}
El uso de tipos avanzados no es una solución fija, pero en muchos casos puede protegernos de casos imposibles, que se darán si empezamos a definir todas nuestras propiedades como opcionales. Por consiguiente, nuestro código será más semántico, y en el momento de desarrollo, podremos apoyarnos en estos tipos para estar seguros de que el funcionamiento es el esperado.
export type Customer = {
firstName: string
middleName?: string
lastName: string
email: string
}
export type UserDigitalAddress = Omit<Customer, 'email'> & { activationEmail: string }
export type UserPhysicalAddress = Customer & { address: Address }
export type UserDigitalPhysicalAddress = UserDigitalAddress & UserPhysicalAddress
export type UserShippingAddress = UserDigitalAddress|UserPhysicalAddress|UserDigitalPhysicalAddress
export type UserBillingAddress = UserPhysicalAddress
¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.
Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?
Pero espera 🖐 que tenemos un conflicto interno. A nosotros las newsletter nos parecen 💩👎👹 Por eso hemos creado la LEAN LISTA, la primera lista zen, disfrutona y que suena a rock y reggaeton del sector de la programación. Todos hemos recibido newsletters por encima de nuestras posibilidades 😅 por eso este es el compromiso de la Lean Lista