paint-brush
El poder de los sindicatos discriminados en TypeScriptpor@tokenvolt
242 lecturas

El poder de los sindicatos discriminados en TypeScript

por Oleksandr Khrustalov4m2024/10/05
Read on Terminal Reader

Demasiado Largo; Para Leer

Esta historia describe los conceptos básicos de las uniones discriminadas en TypeScrips. Las utilizo con bastante frecuencia en mi desarrollo, por lo que veremos un ejemplo concreto sobre cómo aplicarlas.
featured image - El poder de los sindicatos discriminados en TypeScript
Oleksandr Khrustalov HackerNoon profile picture

Planteando el problema

A medida que Typescript crece y gana popularidad recientemente, cada vez más desarrolladores de JavaScript valoran la seguridad de tipos. La lista de características que ofrece Typescript es enorme y puede resultar abrumadora, por lo que en esta publicación me centraré en una de ellas que es fácil de entender y tiene un impacto práctico muy claro.


Comencemos con un ejemplo. Imaginemos que estamos desarrollando una aplicación con muchos roles de usuario. Es bastante común que una aplicación sea utilizada por diferentes usuarios, ¿no es así? Los roles exactos no son realmente importantes aquí, pero digamos que son admin , consumer y guest . En TypeScript, podemos declarar usuarios que tengan esos roles de la siguiente manera:


 type Admin = {} type Consumer = {} type Guest = {}


Ahora, consideremos un conjunto de atributos que tiene cada rol de usuario. Por lo general, son email , firstName y lastName o algo así. Pero, espere, los usuarios Guest probablemente no los tengan (después de todo, son invitados), así que dejemos este tipo vacío por ahora.


 type Admin = { firstName: string lastName: string email: string } type Consumer = { firstName: string lastName: string email: string } type Guest = {}


El usuario de una aplicación solo puede tener un rol. La forma de representar esto a través de tipos es utilizar un tipo union .


 type User = Admin | Consumer | Guest


Los administradores son famosos por sus habilidades exclusivas y, en nuestra aplicación, pueden invitar a los consumidores. Agreguemos un campo que indique cuántas invitaciones puede enviar un administrador.


 type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number // <-- added }


Para hacer las cosas más interesantes y cercanas a una aplicación real, agreguemos una propiedad exclusiva para un tipo Consumer .


 type Consumer = { firstName: string lastName: string email: string premium: boolean // <-- added }


Este es un ejemplo muy simple y, en realidad, los usuarios podrían tener docenas de propiedades dispares, lo que complica considerablemente la base de código cuando necesita acceder a ciertas propiedades.


 const doSomethingBasedOnRole = (user: User) => { // how do you check here that user is really an admin if (user) { // ...and do something with the `numberOfInvitesLeft` property? } }


Una opción es verificar la existencia del inmueble.


 const doSomethingBasedOnRole = (user: User) => { if (user && user.numberOfInvitesLeft) { // safely access `numberOfInvitesLeft` property } }


Pero esta es una solución tediosa y no escalable. ¿Y qué hacer cuando `numberOfInvitesLeft` se convierte en una propiedad opcional?

Presentación de tipos de sindicatos discriminados

Aquí es donde entran en juego los tipos de unión discriminados. Solo tenemos que poner un campo adicional en cada tipo de usuario que indique el rol.


 type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number role: "admin" // <-- added } type Consumer = { firstName: string lastName: string email: string role: "consumer" // <-- added } type Guest = { role: "guest" // <-- added }


Observa cómo pongo una cadena específica como tipo; esto se llama tipo literal de cadena . Esto te permite usar operadores nativos del lenguaje JS, por ejemplo, switch case , if , else para discriminar según el rol.


 const user: Admin = { firstName: "John", lastName: "Smith", email: "[email protected]", numberOfInvitesLeft: 3, role: "admin", } const doSomethingBasedOnRole = (user: User) => { if (user.role === "admin") { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } }


Lo mismo se aplica a una declaración de caso de cambio.


 // ... const doSomethingBasedOnRole = (user: User) => { switch (user.role) { case "admin": { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } case "consumer": { // do something with a `Consumer` user // if you try to call `user.numberOfInvitesLeft` here, TS compiler errors in // // "Property 'numberOfInvitesLeft' does not exist on type 'Consumer'." // } } }


Los beneficios de los tipos de unión discriminados son evidentes porque la verificación de tipos se basa en propiedades de rol explícitas y no en propiedades ad hoc que podrían o no estar relacionadas con un usuario específico.