Tipografía avanzada en TypeScript

Foto Manuel Artero Aguita

Manuel Artero Anguita Seguir

Tiempo de lectura: 3 min

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

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

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.

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)

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

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.

Compártelo en tus redes sociales

Medios de comunicación

Contacta con nuestro departamento de comunicación o solicita material adicional.