DEV Community 👩‍💻👨‍💻

Cover image for Typescript: Uniones discriminadas o como crear argumentos opcionales y dependientes
Matías Hernández Arellano
Matías Hernández Arellano

Posted on • Originally published at matiashernandez.dev

Typescript: Uniones discriminadas o como crear argumentos opcionales y dependientes

Es común tener componentes o funciones que aceptan argumentos que dependen entre sí, dónde una puede estar presente y la otra no.

¿Sabías que con Typescript puedes asegurarte de que este comportamiento se cumpla correctamente?

Veamos una situación imaginaria usando React

Tienes un componente que representa un gato, y este gato puede estar vivo o muerto y este comportamiento o estado está definido por las props que recibe.

function Cat({ isAlive, isDead}) {
  if(isAlive != null && isDead != null) {
    throw 'Ambas props no pueden estar definidas'
  } 
  let str = isAlive ? 'vivo' : 'muerto'

  return <>Este gato esta {str}</>
}


<Cat isAlive isDead />
Enter fullscreen mode Exit fullscreen mode

Pero evidentemente este gato no puede estar en ambos estados a la vez, si no, sería un gato the Schrödinger en vez de un gato real.

Además, el significado de ambas prop isAlive y isDead es similar pero contrario, por lo que para asegurar el correcto funcionamiento tendrías que escribir bloques condicionales que revisen las props y darle prioridad a una sobre otra.

¿Cómo puedes representar esto con typescript y por qué lo harías?

La idea de representar este comportamiento con Typescript es que el equívoco uso de las props sea detectado antes de que el código sea ejecutado y sin necesidad de escribir extra lógica para revisar el contenido de las props.

Para lograrlo harás uso de una combinación de propiedades opcionales, el tipo never y uniones.

/*
* Props de un gato vivo
* Si isAlive está presente entonces isDead no debe estarlo 
*/
type LivingCat = { isAlive: boolean, isDead?: never }

/*
* Props de un gato muerto
* Si isDead está presente entonces isLive no debe estarlo 
*/
type DeadCat = { isAlive?: never, isDead: boolean }

/*
* Crea una unión de las tipos definidos
*/
type RealCatProps = LivingCat | DeadCat 

/*
* Solo una prop puede estar presente. 
* La validación de este comportamiento se realiza en tiempo de compilación
*/
const RealCat = ({ isAlive, isDead}: RealCatProps) => {
    const str = isAlive ? 'living' : 'dead'
    return <>this is a {str} real cat</>
}
Enter fullscreen mode Exit fullscreen mode

En éste código se definen dos tipos muy similare LivingCat y DeadCat la diferencia radica en que propiedad esta definida como never.

En el primer caso, isDead se marca como opcional y como never, es decir, nunca será utilizada o definida.

El tipo never es utilizado cuando estás seguro que "algo" jamás ocurrirá.

En el segundo caso, ocurre lo contrario. La propiedad isLiving está marcada como opcional y como never.

Finalmente, creas una unión de los tipos, creando el tipo RealCatProps y usarás dicho tipo para definir las props del component RealCat.

Este component RealCat podrá ser instanciado sólo con una de las props, pero jamás ambas.

const RealApp = () => {
    return <RealCat isAlive />
}

/*
* Esto falla en tiempo de compilación ya que ambas props están presentes
*/
const ReailFalingApp = () => {
    return <RealCat isAlive={false} isDead={false} />
}
Enter fullscreen mode Exit fullscreen mode

Te invito a jugar con el código en este ejemplo con React o en el playground de typescript

Footer Social Card.jpg
✉️ Únete a Micro-bytes 🐦 Sígueme en Twitter ❤️ Apoya mi trabajo

Top comments (0)

This post blew up on DEV in 2020:

js visualized

🚀⚙️ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! 🥳

Happy coding!