DEV Community

Cover image for Generics de TypeScript
Juan Delgado
Juan Delgado

Posted on

Generics de TypeScript

En lenguajes como C# y Java, una de las principales herramientas de la caja de herramientas para crear componentes reutilizables son los genéricos, es decir, poder crear un posible .Esto permite a los usuarios consumir estos componentes y utilizar sus propios tipos.

Hola Mundo con Generics

Para empezar, hagamos el "hola mundo" de los genéricos: la función de identity. La función de la identity es una función que devolverá lo que se le pase. Puedes pensar en esto de forma similar al comando .echo

Sin los genéricos, tendríamos que dar a la función de identidad un tipo específico:

 function identity(arg: number): number {
    return arg;
 }

O, podríamos describir la función de identidad usando el tipo:any

 function identity(arg: any): any {
    return arg;
 }

Aunque el uso es ciertamente genérico en el sentido de que hará que la función acepte todos los tipos para el type of, en realidad estamos perdiendo la información sobre cuál era ese tipo cuando la función retorne. Si pasamos un número, la única información que tenemos es que cualquier tipo podría ser devuelto. any arg

En cambio, necesitamos una forma de capturar el tipo de argumento de tal manera que también podamos usarlo para denotar lo que se está devolviendo. Aquí, usaremos una type variable, un tipo especial de variable que funciona con tipos en lugar de valores.

function identity<T>(arg: T): T {
    return arg;
}

Ahora hemos añadido una tipo de variable a la función de identidad. Esto nos permite capturar el tipo que el usuario proporciona (por ejemplo), para poder usar esa información más tarde. Aquí, usamos de nuevo como el tipo de retorno. En la inspección, ahora podemos ver que el mismo tipo se utiliza para el argumento y el tipo de retorno. Esto nos permite traficar ese tipo de información en un lado de la función y en el otro.

Decimos que esta versión de la función es genérica, ya que funciona sobre una serie de tipos. A diferencia de la utilización , también es tan precisa (es decir, no pierde ninguna información) como la primera función que utilizó números para el argumento y devuelve type. identity any identity

Una vez que hemos escrito la función de identidad genérica, podemos llamarla de una de dos maneras. La primera forma es pasar todos los argumentos, incluyendo el tipo de argumento, a la función:

let output = identity<string>("myString");  // El tipo de salida será 'string'
console.log(output);

Aquí nos fijamos explícitamente como uno de los argumentos de la llamada a la función, denotando el uso de los argumentos de alrededor en lugar de .T string <> ()

La segunda forma es también quizás la más común. Aquí usamos el tipo de inferencia de argumento, es decir, queremos que el compilador establezca el tipo de argumento por nosotros automáticamente basado en el tipo de argumento que pasamos en: T

let output = identity("myString");  // El tipo de salida será 'string'

Fíjense que no tuvimos que pasar explícitamente el tipo en los corchetes angulares (); el compilador sólo miró el valor , y estableció su tipo. Mientras que la inferencia de los argumentos de tipo puede ser una herramienta útil para mantener el código más corto y más legible, puede ser necesario pasar explícitamente los argumentos de tipo como hicimos en el ejemplo anterior cuando el compilador falla en la inferencia del tipo, como puede suceder en ejemplos más complejos.<> "myString" T

Trabajando con Variables de Tipo Genérico

Cuando comiences a usar genéricos, notarás que cuando creas funciones genéricas, el compilador hará que uses correctamente cualquier parámetro tecleado genéricamente en el cuerpo de la función. Es decir, que realmente tratas estos parámetros como si pudieran ser cualquier tipo de identidad.

Tomemos nuestra función de antes:

function identity<T>(arg: T): T {
    return arg;
}

¿Y si queremos registrar también la longitud del argumento en la consola con cada llamada? Podríamos estar tentados a escribir esto:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

Digamos que en realidad hemos pretendido que esta función funcione en matrices de más que directamente. Ya que estamos trabajando con matrices, el número de miembros debería estar disponible. Podemos describirlo como si creáramos matrices de otros tipos:

function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // La matriz tiene una longitud, así que no hay más errores.
    return arg;
}

Puedes leer el tipo de como "la función genérica toma un parámetro y un argumento que es un array de s, y devuelve un array de s". Si pasamos una serie de números, obtendríamos una serie de números de vuelta. Esto nos permite utilizar nuestra variable de tipo genérico como parte de los tipos con los que estamos trabajando, en lugar de todo el tipo, dándonos una mayor flexibilidad.

Podemos escribir el ejemplo de la muestra de esta manera:

function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // La matriz tiene una longitud, así que no hay más errores.
}

Escribiendo una interfaz genérica

interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;

Clases Genéricas

Una clase genérica tiene una forma similar a una interfaz genérica. Las clases genéricas tienen una lista de parámetros de tipo genérico entre corchetes angulares () siguiendo el nombre de la clase.

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

Este es un uso bastante literal de la clase, pero es posible que haya notado que nada está restringiendo a usar solo el tipo. Podríamos haber usado o incluso objetos más complejos.

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

Al igual que con la interfaz, colocar el parámetro type en la propia clase nos permite asegurarnos de que todas las propiedades de la clase están trabajando con el mismo tipo.

Top comments (0)