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:

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

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:

Esto es posible gracias a Análisis del flujo de control.
¿Y si extraemos una función auxiliar?

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:

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.
Medios de comunicación
Contacta con nuestro departamento de comunicación o solicita material adicional.