Anuncios

Bienvenidos sean a este post, hoy nos meteremos en un tema que a los conocedores de C++ les traera recuerdos de vietnam 🤭

Anuncios

Los genericos son como su nombre lo indican tipos o mejor dicho sintaxis generica. Esta forma de trabajar tiene la particularidad que permite a nuestro codigo trabajar con una amplia variedad de objetos y tipos primitivos. Pero a pesar de lo mencionado anteriormente esto nos permite escribir codigo type-safe porque por ejemplo, al realizar una iteracion sobre un array forzara a que todos los elementos en este sean del mismo tipo. Comencemos a hablar sobre la sintaxis generica.

Anuncios
Anuncios

Al igual que en otros lenguajes, typescript para indicar que es un tipo generico lo hace mediante los signos de mayor y menor (<>) y entre medio de ellos le pasa una letra que actuara como comodin o reemplazo del tipo. Por lo general se asigna la letra T (en mayusculas) y el codigo sabra que donde la encuentre ira el tipo que haya recibido. Puede sonar dificil pero no lo es…. tanto 😅. Para entender el concepto vamos a analizar un ejemplo. Para ello crearemos un archivo con el nombre de genero.ts y le agregaremos el siguiente codigo:

genero.ts

function mostrarGenerico<T>(v: T) {
        console.log(v + " es de tipo: " + (typeof v));
}

mostrarGenerico(1);
mostrarGenerico("tinchicus.com");
mostrarGenerico(true);
mostrarGenerico(() => {});
mostrarGenerico({ id: 1 });
Anuncios

Esta es una funcion que es de tipo generico, para indicarle esto le pasamos el <T> al lado del nombre y en el argumento en lugar de pasar el tipo de dato, como en posts anteriores, le pasamos a T. Esto le indica al codigo que debera adaptar el tipo de argumento al recibido. Suena confuso pero cuando lo apliquemos lo entenderan, Dentro del bloque mostraremos el mensaje recibido concatenado con el tipo de valor. Por ultimo haremos cinco llamadas a la funcion con distintos valores. Compilemos y veamos como es su salida:

$ node genero.js
1 es de tipo: number
tinchicus.com es de tipo: string
true es de tipo: boolean
() => { } es de tipo: function
[object Object] es de tipo: object
$
Anuncios
Anuncios

Esta es la ventaja de manejar tipos genericos. Podemos manejar varios tipos con la misma funcion. Vimos hace tiempo algo similar cuando hablamos de sobrecarga u overload en este post, donde mediante la misma funcion con distintos argumentos podiamos procesar varios tipos de datos. Pero aqui logramos lo mismo pero con una sola funcion porque como dijimos la funcion se adaptara al valor recibido. Miren nuevamente la salida ya que no solo tomo el valor sino que identifica perfectamente cual tipo es. Pero tambien podemos especificar el tipo, para ello tomemos del codigo anterior la siguiente linea:

mostrarGenerico(1);
Anuncios

Y la modificaremos de la siguiente manera:

mostrarGenerico<number>(1);
Anuncios

Esta es una forma de especificarle el tipo de dato que recibira en esta ocasion, si lo compilan con esta modificacion seguira funcionando normalmente pero si a esa linea la modificaran de la siguiente forma:

mostrarGenerico<string>(1);
Anuncios

Le especificamos que reciba un tipo string, veamos que sucede al compilarlo:

$ tsc
genero.ts:5:25 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

5 mostrarGenerico<string>(1);
                          ~


Found 1 error in genero.ts:5

$
Anuncios

Como dijimos es generico siempre y cuando no le asignemos un tipo y utilicemos un comodin como T pero cuando asignemos un tipo de dato solo recibira ese tipo de dato. Pero esto se va a poner mas divertido y para ello modifiquemos el codigo de la siguiente manera:

function mostrarGenerico<A, B>(valor_1: A, valor_2: B) {
        console.log(valor_1 + " es de tipo: " + (typeof valor_1));
        console.log(valor_2 + " es de tipo: " + (typeof valor_2));
}

mostrarGenerico(1, "tinchicus.com");
mostrarGenerico<boolean, Function>(true, ()=>{});
Anuncios
Anuncios

Esto es lo que se denomina tipos multiples genericos porque entre los signos de mayor y menor podemos poner la cantidad que necesitemos separados por comas. Al comienzo mencionamos que se lo asigna con la letra T en general pero no es obligatorio. Podemos usar los que necesitemos, en este caso usamos A y B. Despues al igual que en el codigo anterior a cada argumento le asignammos uno de los tipos genericos, hacemos lo mismo que en el codigo anterior tomamos los valores, los mostramos e indicamos cada tipo pero ahora para los dos argumentos. Luego haremos dos llamados a la funcion, en el primer caso no especificamos ningun tipo pero para el segundo si lo hacemos. Esto nos sirve para mostrar como hacerlo cuando tenemos mas de un tipo generico. Siempre deben respetar la estructura de como se definio. Con todo esto comentado compilemos y veamos la salida:

$ node genero.js
1 es de tipo: number
tinchicus.com es de tipo: string
true es de tipo: boolean
() => { } es de tipo: function
$
Anuncios

Ya vimos que son los tipos genericos, como se pueden limitar tambien asi como tipos multiples pero como dije antes, sigamos que esto recien esta empezando.

Anuncios

Una situacion que necesitamos muchas veces es limitar los tipos que aceptara T para permitir un mejor orden de los tipos que manejaremos. Para entender mejor este concepto vamos a verlo a traves de un ejemplo y para ello analizaremos el siguiente codigo:

class Unidor<T extends Array<string> | Array<number>> {
        public unir(elem: T): string {
                let retorno = "";
                for(let i = 0; i < elem.length; i++) {
                        retorno += i > 0 ? "," : "";
                        retorno += elem[i].toString();
                }
                return retorno;
        }
}

let unidor = new Unidor();
let resultado = unidor.unir([ "uno", "dos", "tres" ]);
console.log("resultado: " + resultado);
resultado = unidor.unir([ 1000,2000,3000 ]);
console.log("resultado: " + resultado);
Anuncios
Anuncios

Primero creamos una clase llamada Unidor donde haremos que el tipo generico, T, sea heredero de Array pero solo de tipos string o number. Despues declaramos un metodo llamado unir que recibira solo elementos de tipo Array pero de los tipos antes mencionados. En el bloque definimos una variable que sera la que devolvemos. Lo siguiente es un bucle que pasara por todo el array informado donde primero usaremos un operador condicional donde verifica si i es mayor que 0 y en caso de ser verdadero le agrega una coma de lo contrario agrega un espacio vacio. Esta se encargara de separar los elementos que agreguemos, los cuales son agregados con la siguiente linea donde obtendremos el valor y lo convertiremos en string mediante ese metodo. Una vez finalizado el bucle devolveremos la variable que completamos. Luego crearemos un objeto de esta clase, para luego definir una variable donde usaremos al metodo anterior y le pasaremos un array como argumento. Despues mostraremos el resultado devuelto, volvemos a hacer lo mismo pero esta vez en lugar de pasar unos string pasaremos unos tipo number y lo mostraremos nuevamente. Compilemos y veamos como es su salida:

$ node genero.js
resultado: uno,dos,tres
resultado: 1000,2000,3000
$
Anuncios

Funciono perfectamente con los dos tipos que asignamos como restriccion, volvamos al codigo anterior y agreguemos las siguientes dos lineas al final del codigo:

resultado = unidor.unir([ true, false, true ]);
console.log("resultado: " + resultado);
Anuncios

En este caso pasamos valores de tipo boolean, compilemos y veamos que sucede:

$ tsc
genero.ts:17:27 - error TS2322: Type 'boolean' is not assignable to type 'string | number'.

17 resultado = unidor.unir([ true, false, true ]);
                             ~~~~

genero.ts:17:33 - error TS2322: Type 'boolean' is not assignable to type 'string | number'.

17 resultado = unidor.unir([ true, false, true ]);
                                   ~~~~~

genero.ts:17:40 - error TS2322: Type 'boolean' is not assignable to type 'string | number'.

17 resultado = unidor.unir([ true, false, true ]);
                                          ~~~~


Found 3 errors in the same file, starting at: genero.ts:17

$
Anuncios

En este caso no permite compilarlo porque este tipo no esta permitido en la clase que creamos pero esto no es unico. Volvamos al codigo y reemplacemos las dos lineas anteriores con las siguientes:

resultado = unidor.unir([ 1000, "dos mil", 3000 ]);
console.log("resultado: " + resultado);
Anuncios

Aqui tenemos una mezcla de tipos permitidos, compilemos y veamos que sucede:

$ tsc
genero.ts:17:25 - error TS2345: Argument of type '(string | number)[]' is not assignable to parameter of type 'string[] | number[]'.
  Type '(string | number)[]' is not assignable to type 'string[]'.
    Type 'string | number' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.

17 resultado = unidor.unir([ 1000, "dos mil", 3000 ]);
                           ~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error in genero.ts:17

$
Anuncios

Ocurrio lo que comentamos al comienzo, por mas que usemos tipos genericos y sean para aceptar cualquier tipo estos estan pensados para mantener el mismo tipo en todo el array, en caso de trabajar con este, y en este caso pese a ser tipos permitidos no podremos usarlos mezclados.

Anuncios

Ya vimos multiples acciones con los tipos genericos asi como recien vimos como restringirlos. Esto ultimo es especialmente util para limitar la cantidad de tipos que podemos utilizar en nuestro genericos. Otro limite que podemos establecer es que solo se referencie funciones o propiedades comunes con el cualquier tipo de T. Analicemos el siguiente codigo:

interface IMostrarID {
        id: number;
        mostrar(): void;
}
interface IMostrarNombre {
        nombre: string;
        mostrar(): void;
}

function generico<T extends IMostrarID | IMostrarNombre>(elem: T)
        : void {
        elem.mostrar();
        elem.id = 1;
        elem.nombre = "tinchicus";
}
Anuncios

Aqui tenemos dos interfaces donde cada una tendra una propiedad pero tienen el mismo metodo. Despues tenemos una funcion donde nuevamente T sera heredera de ambas interfaces pero solo recibe a una o la otra. El argumento debe ser de alguno de estos dos y en el bloque llamaremos al metodo e intentaremos establecer los valores de las propiedades. Compilemos y veamos la salida:

$ tsc
genero.ts:13:7 - error TS2339: Property 'id' does not exist on type 'IMostrarID | IMostrarNombre'.
  Property 'id' does not exist on type 'IMostrarNombre'.

13  elem.id = 1;
         ~~

genero.ts:14:7 - error TS2339: Property 'nombre' does not exist on type 'IMostrarID | IMostrarNombre'.
  Property 'nombre' does not exist on type 'IMostrarID'.

14  elem.nombre = "tinchicus";
         ~~~~~~


Found 2 errors in the same file, starting at: genero.ts:13

$
Anuncios

Observen que nos notifica que las propiedades no existen en la otra interfaz pero no nos dijo nada sobre el metodo. Por lo tanto, ahora mientras no existan los elementos en ambas interfaces no podremos utilizarlos.

Anuncios

Una posibilidad que disponemos es poder construir un tipo generico mas alla de un generico. Esta tecnica basicamente es usar un tipo para restringir otro tipo. Analicemos el siguiente ejemplo:

function mostrar<T, K extends keyof T>
        (objeto: T, clave: K) {
        let valor = objeto[clave];
        console.log("objeto[" + clave.toString() + "]: " + valor);
}

let obj = { id:1, nombre:"tinchicus", print(){ console.log(this.id)}};
mostrar(obj, "id");
mostrar(obj, "nombre");
mostrar(obj, "print");
Anuncios
Anuncios

Primero tenemos una funcion con dos tipos genericos donde el segundo estara restringido por las claves del primer tipo (la devolucion del operador keyof). En los argumentos recibiremos el objeto y la clave los cuales tendran los tipos genericos que establecimos anteriormente. Despues en el bloque obtendremos el valor de la clave informada y la almacenamos en una variable para luego mostrar el valor. Luego crearemos un objeto con varios elementos en su interior y haremos tres llamados a la funcion anterior pasando el objeto y tres claves que estan en este. Compilemos y veamos como es su salida:

$ node genero.js
objeto[id]: 1
objeto[nombre]: tinchicus
objeto[print]: print() { console.log(this.id); }
$
Anuncios

Como podemos ver funciono perfectamente, tomemos nuevamente el codigo anterior y agreguen la siguiente linea al final del codigo:

mostrar(obj, "apellido");
Anuncios

Un llamado a una clave que no existe en la tabla. Compilemos y veamos como es su salida:

$ tsc
genero.ts:11:14 - error TS2345: Argument of type '"apellido"' is not assignable to parameter of type '"id" | "nombre" | "print"'.

11 mostrar(obj, "apellido");
                ~~~~~~~~~~


Found 1 error in genero.ts:11

$
Anuncios

En este caso al pasar un valor de clave que no existe nos informa que no puede ser asignado entre los que obtuvo del objeto informado.

Anuncios

Pero lo generico tambien podemos aplicarlo a una interfaz o clase. Vamos a analizar el siguiente codigo:

interface IMostrar {
        mostrar(): void;
}
interface IRegistroInterface<T extends IMostrar> {
        registrar(iMostrarObj: T): void;
}
class Registro<T extends IMostrar> implements IRegistroInterface<T> {
        registrar(iMostrarObj: T): void {
                iMostrarObj.mostrar();
        }
}
let mostrarObjeto: IMostrar = {
        mostrar() { console.log("mostrarObjeto.mostrar() llamado"); }
};
let registro = new Registro();
registro.registrar(mostrarObjeto);
Anuncios
Anuncios

Primero creamos una interfaz con un solo prototipo de un metodo. Lo siguiente es una interfaz nueva pero que sera heredera de la anterior, en esta a su vez declaramos un prototipo de un metodo. Lo siguiente sera una clase donde el tipo generico sera heredero y restringido por la primera interfaz pero en esta implementaremos a la segunda interfaz. Aqui definiremos el metodo de la segunda interfaz donde recibiremos un objeto pero del tipo de la primera interfaz (recuerden que lo hicimos con la restriccion de T) y en el bloque usaremos al objeto recibido para llamar al metodo de la interfaz. Continuando con el codigo, lo primero que haremos sera definir un objeto del tipo de la primera interfaz pero en esta le pasaremos una funcion que se llamara igual que el prototipo, en realidad esta sera la definicion del metodo de la interfaz, y luego crearemos un objeto pero de la clase Registro y desde este objeto llamaremos a registrar y a este metodo le pasaremos el objeto anterior. Con esto comentado pasemos a compilar y ver la salida:

$ node genero.js
mostrarObjeto.mostrar() llamado
$
Anuncios

Observen que no fue necesario definir el metodo en el codigo sino que pudimos hacerlo al momento de pasarle el objeto con la definicion y en registrar estaba el llamado al mismo.

Anuncios

Ya hemos visto muchas formas de trabajar y algunas mecanicas interesantes pero ahora veremos como crear objetos dentro de genericos. Pasemos a analizar el siguiente codigo:

class Perro {
        nombre: string | undefined;
        hablar(): void {
                console.log(this.nombre + " dice Guau!");
        }
}
class Gato {
        nombre: string | undefined;
        hablar(): void {
                console.log(this.nombre + " dice Miau!");
        }
}

function creadorInstancias<T>(inst: { new(): T}): T {
        return new inst()
}

let instanciaPerro = creadorInstancias(Perro);
instanciaPerro.nombre="Arturo";
instanciaPerro.hablar();
Anuncios
Anuncios

Aca tenemos dos clases con la misma propiedad y metodo, ambos toman el valor de la propiedad y le concatenan una cadena pero cada una representa el sonido del animal de la clase.. Despues definimos una funcion para crear instancias. Le ponenos el identificador de generico y la magia se realiza en el argumento de la funcion. El identificador y en donde va el tipo de dato le pasamos un new, este sobrecarga (overload) al new para devolver el generico. En el bloque devolvemos la creacion de la instancia de la clase recibida gracias a la sobrecarga del argumento. Por ultimo creamos un objeto con esta funcion, le pasamos la clase Perro, y luego llenamos la propiedad nombre y llamamos al metodo. Compilemos y veamos la salida:

$ node genero.js
Arturo dice Guau!
$
Anuncios

En resumen, hoy hemos visto a genericos, que es, para que sirve, como se utiliza, sus distintas variantes, asi como tambien mecanicas y varios ejemplos para ver como trabaja. Espero les haya sido de utilidad y si los dominan estan accediendo a programacion de muy alto nivel, recuerden que pueden seguirme 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.

Anuncios
pp258

Donación

Es para mantenimento del sitio, gracias!

$1.50