Bienvenidos sean a este post, hoy veremos una variante de type.
En este post vimos al operador condicional, el cual es una version primitiva de if…else, donde usamos una condicion y si se cumple devolvemos de lo contrario otro valor. Esta mecanica podemos aplicarla a type. Analicemos el siguiente codigo:
type NumeroOCadena<T> = T extends number ? number : string;
function elegir<T>(e: NumeroOCadena<T>) {
console.log("elegir: " + e);
}
elegir<number>(1);
elegir<string>("prueba");
Primero definimos un type, en este caso noten que al generico lo haremos heredero de… y aqui entra en accion el condicional. Si recibe un tipo number se lo pasa de lo contrario le pasamos un string. Lo siguiente es una funcion donde el argumento tendra el alias de tipo anterior y en el bloque simplemente lo mostrarmos. Por ultimo, haremos dos llamados a esta funcion y le pasaremos los tipos y un valor del mismo tipo. Compilemos y veamos como es su salida:
$ node tipos.js
elegir: 1
elegir: prueba
$
Hasta aca tenemos todo funcional pero que sucederia con otro tipo? Agreguemos la siguiente linea al final del codigo:
elegir<boolean>(true);
Compilemos y veamos la salida:
$ tsc
tipos.ts:9:17 - error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'string'.
9 elegir<boolean>(true);
~~~~
Found 1 error in tipos.ts:9
$
Recuerden que solo permitimos number o string y este al no pertencer a ninguno de los dos, el else solo devuelve string, no podremos utilizarlo. Modifiquemos la linea anterior de la siguiente manera:
elegir<boolean>("tinchicus");
Mantenemos el tipo boolean pero le pasamos un string, compilemos y veamos como es su salida:
$ node tipos.js
elegir: 1
elegir: prueba
elegir: tinchicus
$
Este funciono porque en realidad nuestro condicional no verifica el tipo recibido sino mas el bien el argumento y en base al tipo de este haremos al generico heredero de este ultimo.
Una opcion interesante a lo visto anteriormente es poder encadenar condicionales para lograr tener mas posibilidades. Analicemos el siguiente codigo:
interface IInt1 {
a: number;
}
interface IInt2 {
a: number;
b: string;
}
interface IInt3 {
a: number;
b: string;
c: boolean;
}
type interfaces<T> =
T extends IInt3 ? [number, string, boolean] :
T extends IInt2 ? [number, string] :
T extends IInt1 ? [number] :
never;
function getTuple<T>(tuple: interfaces<T>): string {
let [...tupleDestructurada] = tuple;
let retorno = "|";
for(let valor of tupleDestructurada) {
retorno += valor + "|";
}
return retorno;
}
console.log(getTuple<IInt1>([1]));
console.log(getTuple<IInt2>([1,"prueba"]));
console.log(getTuple<IInt3>([1,"prueba",true]));
Primero definiremos tres interfaces donde son similares pero de una a otra iremos agregando mas propiedades. .Lo siguiente es un type para representar todas las interfaces anteriores y para ello volveremos a usar el tipo condicional pero de una forma muy particular. Observen que volvemos a usar la misma estructura donde el generico sera heredero de una de las interfaces anteriores. En el primer caso usaremos a IInt3 y devolveremos esa estructura de tuple, en else volvemos a repetir la misma estrctura pero el condicional sera con IInt2 y en caso de ser verdadero devolveremos un tuple igual a las propiedades que posee este. Nuevamente en else volvemos a repetir la misma estructura pero con IInt1 y su propiedad y en el ultimo else devolveremos un never.
Nota:
Sobre el tipo never hablamos en este post.
La funcion recibira argumentos de las posibles interfaces y devolveremos un string. Primero definiremos un tuple con un operador de propagacion para poder recibir multiples valores y le pasaremos el argumento que recibiremos. Lo siguiente es definir la variable que devolveremos y seguida a esta usaremos un bucle for para obtener todos los valores en el tuple anterior y lo agregaremos a la variable anterior, para una vez terminado devolver el valor final de la variable. Por ultimo, haremos tres llamados a esta funcion con todas las posibilidades. Compilemos y veamos como es la salida:
$ node tipos.js
|1|
|1|prueba|
|1|prueba|true|
$
Esta forma de trabajar con un type condicional nos puede resultar muy util e inclusive nos puede ser util para aplicar en el operador condicional.
Una particularidad que disponemos tambien con el type condicional es que nos limita a devolver un solo tipo sino que podemos devolver varios, esto se lo denomina como type condicional distribuido. Analicemos el siguiente codigo:
type variosTipos<T> =
T extends Date ? Date :
T extends number ? Date | number :
T extends string ? Date | number | string :
never;
function mostrar<T>(e: variosTipos<T>) {
console.log(e);
}
mostrar<number>(1);
mostrar<string>("tinchicus");
Primero definiremos el type, aqui volveremos a usar el type condicional encadenado pero con una variacion. En esta ocasion lo haremos para tres tipos: Date, number y string; en el primer caso si coincide pasaremos ese tipo de dato, en caso contrario volvemos a repetir la operacion pero para number. Si es afirmativo no solo devolveremos el tipo number porque le aplicamos la union de tipos con el tipo anterior. De nuevo en el caso contrario usaremos nuevamente la estructura para verificar si es string y en caso de coincidir devuelve los dos tipos anteriores y el string todos unidos y ahora con el ultimo else devolvemos never para cerrar el condicional. Despues definiremos una funcion donde recibiremos un valor y usaremos el type anterior. Donde si se encuentra dentro de los rangos posibles lo mostraremos. Lo ultimo es llamar dos veces a esta ultima funcion con diferentes datos. Compilemos y veamos la salida:
$ node tipos.js
1
tinchicus
$
Como pueden ver funciono porque le informamos tipos de datos que se encuentran dentro del rango posible. Si prueban con otros tipos veran que nos devolvera un fallo.
Un forma de mejorar al type condicional es gracias a la inferencia y para ello pasemos a analizar el siguiente codigo:
type inferencia<T> =
T extends { id: infer U } ? U : never;
function testeo<T>( a: inferencia<T> ) {}
testeo<{ id: string }>("tinchicus");
testeo<{ id: number }>(1);
Nuestro type mantiene un poco de lo visto anteriormente donde haremos que T sea heredero de otro tipo. Aqui viene el primer cambio, en este caso recibe una propiedad llamada id donde mediante infer estamos estableciendo un nuevo tipo generico. En este caso sera el que usemos para ser base de T pero la condicion para cumplirlo es que pasemos a id y el tipo. En caso de cumplirse devolveremos el tipo contenido en U de lo contrario devuelve un never. Despues tenemos una funcion generica que recibe un argumento del tipo anterior pero no hara nada. Por ultimo llamaremos dos veces a esta funcion y observen como es la sintaxis. Siempre pasaremos donde poniamos el tipo generico las dos llaves con la propiedad id y el tipo que usaremos, en el argumento solo pasaremos el dato.
Esto debe funcionar perfectamente pero no es lo unico que podemos hacer, esto lo podemos hacer con una firma de funciones. Tomemos el codigo anterior y hagamos unas modificaciones:
type inferencia<T> =
T extends { id: infer U } => void ? U : never;
function testeo<T>( a: inferencia<T> ) {}
testeo<{ id: string } => void>("tinchicus");
testeo<{ id: number } => void>(1);
Este type es similar al anterior pero ahora le agregamos una firma de funcion donde usaremos el argumento de esta para recibir el tipo y a su vez debemos devolver un void para cumplir la condicion y devolver el nuevo tipo generico. El resto sigue siendo lo mismo, lo mismo con la funcion pero la siguiente modificacion es en el llamado donde ahora debemos agregar la flecha de funcion y pasar que devolvera un void. Hasta aca no es mucha la diferencia pero esta forma nos permite unas variaciones interesantes para probar. Tomemos el codigo anterior y realicemos la siguiente modificacion:
type inferencia<T> =
T extends ( id: string ) => infer U ? U : never;
function testeo<T>( a: inferencia<T> ) {}
testeo<( id: string ) => number>(1);
testeo<( id: string ) => boolean>(true);
La primera modificacion es que ahora id solo recibe de tipo string pero es el argumento de la funcion y esta mediante infer devolvera la que informamos. Y esta sera la que cumpla la condicion y la que usaremos con T. Observen que al llamarlo designamos que el id es de tipo string pero la salida de la funcion son de dos tipos diferentes y id ahora pasara a ser pura y exclusivamente argumento de la funcion. Seguimos con la misma funcion pero ahora hicimos una modificacion mas al pasar como el tipo generico. En este caso respetamos su firma como es la definicion del argumento y luego usamos la misma flecha de funcion donde ahora si pasaremos el tipo que sera asignado a U para despues pasar el valor correspondiente. Es similar al anterior pero solo con el tipo que devuelve una funcion.
A su vez la inferencia tiene una opcion mas como es a traves de un array, vamos a tomar el codigo anterior y lo modificaremos de la siguiente manera:
type inferencia<T> =
T extends (infer U)[] ? U : never;
function testeo<T>( a: inferencia<T> ) {}
testeo<string[]>("tinchicus");
testeo<boolean[]>(true);
Es la misma estructura pero al momento de establecer cual sera el tipo informado como hasta ahora tenemos entre parentesis al operador infer del nuevo tipo, haciendo todo lo mismo que hasta ahora pero con el agregado de los signos de array por lo tanto aqui estableceremos que tipo de array se usara y el condicional seguira haciendo lo mismo. Despues la siguiente modificacion es al momento de llamar a la funcion donde en el generico pasamos el tipo de array con su respectivos signos y luego el o los datos que contendra, en este caso es bien simple y un dato para cada uno.
En el post anterior vimos unos type mapeados muy particulares para trabajar ciertas acciones. Estas eran Partial, Readonly, Pick y Record pero el type que hablamos en este post tambien posee los suyos. Vamos a comenzar del primero mediante el siguiente codigo:
type Excluir = Exclude<string|number|boolean, string|number>;
let valor: Excluir = true;
Esta es la primera de las mencionadas, Exclude recibira dos argumentos, el primero sera un grupo de tipos (todos los necesarios) y el segundo sera las que deseamos excluir de ese grupo. En todos los casos usaremos el operador de union para pasar todos los necesarios. Por ultimo creamos una variable con el tipo Excluir y pasamos un boolean, si lo compilan funcionara correctamente pero si en lugar de true pasan uno de los tipos excluidos nos devolvera el siguiente mensaje al compilarlo:
error TS2322: Type 'string' is not assignable to type 'Excluir'.
Lo mismo ocurriria con un number. Para ver el siguiente de estos necesitamos ver el siguiente codigo:
type Extraer = Extract<string|number|boolean, string>;
let valor: Extraer = "true";
Este es similar al anterior pero hace lo opuesto. Primero le informamos un grupo de tipos y en el segundo argumento le informamos los que seleccionaremos para utilizar. Por ejemplo en este caso le decimos que solo «extraiga» el de tipo string. Por lo tanto al momento de usarlo funcionara en este caso pero al igual que sucede anteriormente nos devolvera un error si usamos otro tipo, les dejo un ejemplo:
error TS2322: Type 'boolean' is not assignable to type 'string'.
Solo nos resta ver el ultimo de estos, al igual que en los casos anteriores vamos a hacerlo mediante un ejemplo:
type NoNulo = NonNullable<string|boolean|undefined|null>;
let valor: NoNulo = true;
Este lo unico que realiza es no permitir que se le asigne el valor undefined y null a la variable que trabajemos pero si permitira el resto de tipos que informemos. Pero esto no aplica a si no definimos la funcion, si solo la declaramos no se vera afectada. Pero si le asignamos un tipo no incluido o tanto undefined como null nos enviara una notificacion de error. Para ir finalizando estos tres usualmente se los denomina como estandares de type condicional.
En resumen, hoy hemos visto type condicional, que es, para que sirve, como se utiliza, sus distintas variantes, asi como tambien algunos ejemplos para verlo en accion, espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
