Distrito Telefónica. Hub de Innovación y Talento

Volver
Development

Tipografía avanzada en TypeScript

Digamos que estamos construyendo una aplicación relacionada con la venta de tickets 🎟️. Vamos a gestionar vales, códigos de descuento, pases mensuales. pases…? Para este ejercicio nos centraremos en tres tipos de tickets: Tipos de tickets que debemos tener en cuenta Imagen autogenerada con Midjourney mostrando 3 tickets diferentes; cupón, entrada y pase

Empecemos con 3x modelos de tipografía:

Definiciones de tipos en TypeScript

Definiciones de tipos en TypeScript

Típico de los puestos técnicos, nuestros modelos parecen estar perfectamente preparados para extraer propiedades comunes 🌈
Resaltando las propiedades comunes de los tres tipos

Resaltando las propiedades comunes de los tres tipos

Si te preguntas por qué utilizamos type en lugar de interface, podemos tratar este tema concreto en otro post. En resumen: prefiere type en lugar de interface para definir las propiedades de los objetos. Recuerda, se llama Typescript y no InterfaceScript

Nota: la propiedad ticketType indica qué tipo está cumpliendo de las 3 opciones. Definimos su tipo como un Tipo de unión.

type TicketType = 'event-ticket' | 'coupon' | 'pass';

type CommonTicket = {
  ticketType: TicketType;
  code: string;
  description?: string;
}
type EventTicket = CommonTicket & {
  ticketType: 'event-ticket';    
 event: {
    eventId: string;
    eventName: string;
    eventDate: Date;
  };
};
type Coupon = CommonTicket & {
  ticketType: 'coupon';
  discountAmount: number;
  minPurchaseAmount?: number;
  expirationDate?: Date;
};
type Pass = CommonTicket & {
  ticketType: 'pass';
  startDate: Date;
  endDate: Date;
  remainingUses?: number;
  service: {
    serviceId: string;
    serviceName: string;
  };
};
type Ticket = EventTicket | Coupon | Pass;

Hasta aquí, todo bien.

En algún punto de nuestro código terminaremos con una función con diferentes ramas lógicas dependiendo del tipo de ticket. Estoy 101 % seguro.

VsCode (y supongo que cualquier editor avanzado) es capaz de ayudarnos a través de ifstatements:
 En la tercera rama - hemos comprobado event-ticket y pass, por lo que la input Ticket es un Coupon - VsCode infiere el tipo de Coupon correctamente:
Captura de pantalla de una función en la que primero comprobamos si hay un ticket de evento; el IDE infiere el tipado correcto.

Captura de pantalla de una función en la que primero comprobamos si hay un ticket de evento; el IDE infiere el tipado correcto.

Esto es posible gracias a Análisis del flujo de control.

¿Y si extraemos una función auxiliar?

Captura de pantalla con un error de typescript (explicado a continuación)

Captura de pantalla con un error de typescript (explicado a continuación)

Ahora estamos recibiendo un desagradable Property 'event' does not exist on type 'Ticket'.

Estamos perdiendo el estrechamiento para ticket al extraer la comprobación de tipo a una función.

Predicados de tipo al rescate

El truco consiste en utilizar un Predicado de tipo

-function isAnEvent(ticket: Ticket) {
+function isAnEvent(ticket: Ticket): ticket is EventTicket {
  return ticket.ticketType === 'event-ticket';
}
isAnEvent() no está devolviendo solo un booleano, ahora está definiendo el tipo de guarda que necesitamos.
 Directamente de la documentación:

Cada vez que se llame a [..] isAnEvent()[..] con alguna variable, TypeScript reducirá esa variable a ese tipo específico si el tipo original es compatible.

Podríamos haber saltado a la solución del predicado de tipo, pero de esta forma intento compartir mi proceso mental.

Sobrecarga

Otra herramienta que recomiendo en nuestras cajas de herramientas 🧰 es Sobrecarga de funciones. Siguiendo con el ejemplo que estamos utilizando, Si nos encontramos en una situación como esta:
function getTicket(type: TicketType, code: string): Promise<Ticket> {
  // when type is 'event-ticket' -> EventTicket
  // when type is 'coupon' -> Coupon
  // when type is 'pass' -> Pass
}
Nos gustaría decirle a Typescript,

"Hey, podemos decir el estrechamiento exacto para Ticket dependiendo de la entrada".

Por alguna razón, al principio intenté utilizar Generics. Buscando algún tipo de "Type-Bounding" raro.

Mientras que la solución es mucho más sencilla. A veces basta con escribir lo que necesitas:
  ...una especie de sensación .cpp aquí ¿verdad?
Estamos sobrecargando getTicket(); Typescript inferirá el tipo exacto como un encanto:

Inferencia correcta de tipos

Inferencia correcta de tipos

En este post, hemos explorado los conceptos de predicados de tipo, estrechamientos, y sobrecarga de funciones en TypeScript. Se trata de potentes herramientas para desarrolladores.
Con unos pocos trucos, podemos mejorar nuestras habilidades tipográficas, y mejorar la fiabilidad y flexibilidad de nuestro código TypeScript.

Gracias por dedicar tiempo a leer este post.