Usando generics avanzados en TypeScript


TypeScript ofrece una potente funcionalidad llamada Generics que permite crear componentes y funciones altamente reutilizables y seguros en tiempo de compilación. Los Generics avanzados van más allá de lo básico y permiten escribir código flexible sin perder el tipado estricto.

En este artículo, exploraremos:

  • Cómo funcionan los Generics en TypeScript.
  • Uso de restricciones (extends).
  • Aplicación de múltiples tipos genéricos.
  • Uso de tipos condicionales y mapped types.
  • Casos prácticos en funciones, clases e interfaces.

1. Introducción a Generics en TypeScript

Los Generics permiten escribir código reutilizable sin perder seguridad en el tipado.

Ejemplo básico de una función genérica:

function identidad<T>(valor: T): T {
  return valor;
}

const numero = identidad<number>(42);
const texto = identidad<string>("Hola TypeScript");

Aquí, T representa un tipo genérico que será inferido por TypeScript en tiempo de ejecución.

2. Uso de Restricciones con extends

Podemos restringir los tipos que un Generic puede aceptar usando extends.

Ejemplo con restricción de objeto:

interface Persona {
  nombre: string;
  edad: number;
}

function mostrarNombre<T extends Persona>(obj: T): string {
  return obj.nombre;
}

const usuario = { nombre: "Carlos", edad: 30, rol: "Admin" };
console.log(mostrarNombre(usuario)); // Carlos

Aquí, T debe cumplir con la estructura de Persona, lo que garantiza que el objeto pasado tenga un nombre.

3. Uso de Múltiples Tipos Genéricos

Podemos definir múltiples tipos genéricos en una función o clase:

function combinar<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

const resultado = combinar<number, string>(42, "Hola");
console.log(resultado); // [42, 'Hola']

Este método permite combinar distintos tipos en un solo resultado sin perder la inferencia de tipos.

4. Uso de Tipos Condicionales (Conditional Types)

Los tipos condicionales permiten definir un tipo basado en otro.

type EsString<T> = T extends string ? "Es una cadena" : "No es una cadena";

type Caso1 = EsString<string>;  // "Es una cadena"
type Caso2 = EsString<number>;  // "No es una cadena"

Esto es útil para definir tipos que dependen de otros de manera flexible.

5. Uso de Mapped Types con Generics

Los Mapped Types permiten transformar tipos basados en un tipo genérico.

Ejemplo:

type Opcional<T> = {
  [K in keyof T]?: T[K];
};

interface Usuario {
  id: number;
  nombre: string;
  email: string;
}

type UsuarioOpcional = Opcional<Usuario>;

const user: UsuarioOpcional = { nombre: "Carlos" }; // Correcto

Este tipo genérico convierte todas las propiedades de T en opcionales.

6. Aplicación en Clases

Los Generics también son útiles en clases para manejar datos de forma estructurada.

class Caja<T> {
  private contenido: T;

  constructor(valor: T) {
    this.contenido = valor;
  }

  obtenerContenido(): T {
    return this.contenido;
  }
}

const cajaNumero = new Caja<number>(10);
console.log(cajaNumero.obtenerContenido()); // 10

const cajaTexto = new Caja<string>("Hola Mundo");
console.log(cajaTexto.obtenerContenido()); // Hola Mundo

Aquí, la clase Caja<T> puede almacenar cualquier tipo sin perder seguridad en el tipado.

7. Uso Avanzado con keyof y in

keyof nos permite trabajar con las claves de un objeto en TypeScript.

function obtenerValor<T, K extends keyof T>(obj: T, clave: K): T[K] {
  return obj[clave];
}

const usuario = { id: 1, nombre: "Carlos" };
console.log(obtenerValor(usuario, "nombre")); // Carlos

Aquí K solo puede ser una clave existente dentro de T, evitando errores en tiempo de compilación.

8. Conclusión

Los Generics avanzados en TypeScript permiten escribir código reutilizable, seguro y altamente flexible.


Ver también

comments powered by Disqus