Generic Types en TypeScript, desarrollo reutilizable y adaptable
Tags: typescript, javascript, frontend, programming
Published: 2024-03-04 16:24:34

Como desarrolladores frontend, comprendemos la importancia de crear componentes **reutilizables** y **adaptables**. Los **Generic Types** de **TypeScript** vienen al rescate, permitiéndonos escribir código que funcione perfectamente con varios tipos de datos sin sacrificar la seguridad de tipos.

---

## ¿Qué son los Tipos Genéricos?

Imagina una función que pueda manejar tanto cadenas de texto como números. En lugar de definir funciones separadas para cada tipo, los tipos genéricos te permiten crear una única función que trabaja con un tipo de marcador de posición (a menudo representado por letras como `T`, `U` o `V`). Este marcador de posición actúa como una plantilla, permitiendo que la función se adapte a diferentes tipos de datos cuando se usa:

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

const nombre = identidad<string>("Alice"); // Tipo: cadena de texto
const edad = identidad<number>(30); // Tipo: número
```

## Casos de Uso Comunes en el Desarrollo Frontend:

### 1. Componentes Reutilizables:
 
Crea componentes que pueden aceptar y trabajar con diferentes tipos de datos:

```typescript
interface ElementoLista<T> {
  datos: T;
}

const elementosLista: ElementoLista<string | number>[] = [
  { datos: "Elemento 1" },
  { datos: 2 },
];

function renderizarLista<T>(elementos: ElementoLista<T>[]): string {
  let lista = "";
  for (const elemento of elementos) {
    lista += `<li>${elemento.datos}</li>`;
  }
  return lista;
}

// Ejemplo de uso:
const listaHTML = renderizarLista(elementosLista);
console.log(listaHTML);
```

### 2. Interacciones Seguras con APIs:

Garantiza la seguridad de tipos al interactuar con APIs que devuelven diferentes estructuras de datos:

```typescript
interface RespuestaAPI<T> {
  datos: T;
  estado: number;
}

async function obtenerDatos<T>(url: string): Promise<RespuestaAPI<T>> {
  // ... lógica de obtención
}

const datosUsuario = await obtenerDatos<Usuario>("https://api.ejemplo.com/usuarios/1");
// datosUsuario tiene tipo RespuestaAPI<Usuario>, garantizando la seguridad de tipos
```

### 3. Funciones Utilitarias:

Crea funciones que operen en diferentes tipos de datos con una lógica consistente:

```typescript
function mapearValores<T, U>(valores: T[], mapeador: (valor: T) => U): U[] {
  return valores.map(mapeador);
}

const cadenas = mapearValores(["manzana", "banana"], (cadena) => cadena.toUpperCase()); // cadenas: string[]
const numeros = mapearValores([1, 2, 3], (numero) => numero * 2); // numeros: number[]
```

---

## Técnicas Avanzadas con Tipos Genéricos

Más allá de los usos fundamentales, los tipos genéricos ofrecen funcionalidades avanzadas para mejorar aún más tu desarrollo frontend:

### 1. Tipos Condicionales:

Los tipos genéricos se pueden combinar con lógica condicional para crear tipos basados en condiciones de tiempo de ejecución. Esto permite un manejo dinámico de tipos dentro de la definición genérica:

```typescript
type ArregloCadenasONumeros<T> = T extends string ? string[] : T extends number ? number[] : never;

const arregloCadenas: ArregloCadenasONumeros<string> = ["manzana", "banana"];
const arregloNumeros: ArregloCadenasONumeros<number> = [1, 2, 3];

// Esto daría como resultado un error de tipo:
// const arregloInvalido: ArregloCadenasONumeros<boolean> = [true, false];
```

### 2. Tipos Mapeados:

Estos tipos involucran transformar las propiedades de un tipo existente en un tipo nuevo. Esto puede ser útil para crear funciones utilitarias o iterar sobre las propiedades de un objeto con seguridad de tipos:

```typescript
interface Usuario {
  nombre: string;
  edad: number;
}

type UsuarioMayúsculas<T extends Usuario> = {
  [P in keyof T]: T[P] extends string ? Uppercase<T[P]> : T[P];
};

const usuarioMayúsculas: UsuarioMayúsculas<Usuario> = {
  nombre: "JUAN",
  edad: 30,
};
```

### 3. Tipos Utilitarios Genéricos:

TypeScript proporciona tipos utilitarios genéricos incorporados como `Partial<T>`, `Pick<T, K>` y `Readonly<T> `que simplifican las manipulaciones comunes de tipos. Además, puedes crear tipos utilitarios personalizados para necesidades específicas:

```typescript
type Nullable<T> = T | null; // Permite valores nulos para cualquier tipo

type Omitir<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; // Excluye propiedades específicas

const datosUsuarioOpcionales: Nullable<Usuario> = { nombre: "Alice" }; // Válido

const infoUsuario: Omitir<Usuario, "edad"> = { nombre: "Bob" }; // Válido (excluye "edad")
```

---

## Beneficios de Usar Tipos Genéricos:

- **Mayor Reutilización**: Los componentes genéricos se pueden usar con varios tipos de datos, eliminando la necesidad de código repetitivo para cada tipo específico.
- **Seguridad de Tipos Mejorada**: La verificación de tipos se aplica incluso cuando se utilizan tipos genéricos, lo que evita errores en tiempo de ejecución debido a tipos de datos incorrectos.
- **Estructura de Código Más Clara**: Los tipos genéricos promueven un código mantenible al expresar explícitamente los tipos de datos esperados para componentes y funciones.

## Conclusión

Los tipos genéricos te permiten crear componentes y funciones adaptables y seguras para tipos en tu desarrollo frontend. Al incorporarlos a tu flujo de trabajo, puedes impulsar la reutilización del código, la mantenibilidad y la calidad general de la aplicación. Aprovecha el poder de los genéricos para abordar diversos tipos de datos con confianza y construir experiencias frontend robustas.

## Recursos

- Official TypeScript Documentation: https://www.typescriptlang.org/docs/ - La documentación oficial proporciona explicaciones completas, ejemplos y referencias..
- Learn TypeScript Generics: https://www.freecodecamp.org/news/how-typescript-generics-work/ - Este tutorial interactivo de FreeCodeCamp lo guía a través de los conceptos básicos con ejemplos prácticos.