Español

Explora los genéricos avanzados de TypeScript: restricciones, tipos de utilidad, inferencia y aplicaciones prácticas para escribir código robusto y reutilizable en un contexto global.

TypeScript Generics: Patrones de Uso Avanzados

Los genéricos de TypeScript son una característica poderosa que te permite escribir código más flexible, reutilizable y con seguridad de tipos. Te permiten definir tipos que pueden funcionar con una variedad de otros tipos manteniendo la comprobación de tipos en tiempo de compilación. Esta publicación de blog profundiza en patrones de uso avanzados, proporcionando ejemplos prácticos e ideas para desarrolladores de todos los niveles, independientemente de su ubicación geográfica o antecedentes.

Comprensión de los Fundamentos: Un Resumen

Antes de sumergirnos en temas avanzados, repasemos rápidamente los conceptos básicos. Los genéricos te permiten crear componentes que pueden funcionar con una variedad de tipos en lugar de un solo tipo. Declara un parámetro de tipo genérico entre corchetes angulares (`<>`) después del nombre de la función o clase. Este parámetro actúa como un marcador de posición para el tipo real que se especificará más adelante cuando se utilice la función o clase.

Por ejemplo, una función genérica simple podría verse así:

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

En este ejemplo, T es el parámetro de tipo genérico. La función identity toma un argumento de tipo T y devuelve un valor de tipo T. Luego, puedes llamar a esta función con diferentes tipos:


let stringResult: string = identity("hello");
let numberResult: number = identity(42);

Genéricos Avanzados: Más Allá de lo Básico

Ahora, exploremos formas más sofisticadas de aprovechar los genéricos.

1. Restricciones de Tipo Genérico

Las restricciones de tipo te permiten restringir los tipos que se pueden usar con un parámetro de tipo genérico. Esto es crucial cuando necesitas asegurarte de que un tipo genérico tenga propiedades o métodos específicos. Puedes usar la palabra clave extends para especificar una restricción.

Considera un ejemplo en el que deseas que una función acceda a una propiedad length:

function loggingIdentity(arg: T): T {
  console.log(arg.length);
  return arg;
}

En este ejemplo, T está restringido a tipos que tienen una propiedad length de tipo number. Esto nos permite acceder de forma segura a arg.length. Intentar pasar un tipo que no cumpla con esta restricción provocará un error en tiempo de compilación.

Aplicación Global: Esto es particularmente útil en escenarios que involucran el procesamiento de datos, como trabajar con arreglos o cadenas, donde a menudo necesitas conocer la longitud. Este patrón funciona igual, independientemente de si estás en Tokio, Londres o Río de Janeiro.

2. Uso de Genéricos con Interfaces

Los genéricos funcionan a la perfección con las interfaces, lo que te permite definir definiciones de interfaz flexibles y reutilizables.

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

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

let myIdentity: GenericIdentityFn = identity;

Aquí, GenericIdentityFn es una interfaz que describe una función que toma un tipo genérico T y devuelve el mismo tipo T. Esto te permite definir funciones con diferentes firmas de tipo manteniendo la seguridad de tipos.

Perspectiva Global: Este patrón te permite crear interfaces reutilizables para diferentes tipos de objetos. Por ejemplo, puedes crear una interfaz genérica para objetos de transferencia de datos (DTO) utilizados en diferentes API, asegurando estructuras de datos consistentes en toda tu aplicación, independientemente de la región donde se implemente.

3. Clases Genéricas

Las clases también pueden ser genéricas:


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

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

Esta clase GenericNumber puede contener un valor de tipo T y definir un método add que opera sobre el tipo T. Instancias la clase con el tipo deseado. Esto puede ser muy útil para crear estructuras de datos como pilas o colas.

Aplicación Global: Imagina una aplicación financiera que necesita almacenar y procesar varias monedas (por ejemplo, USD, EUR, JPY). Podrías usar una clase genérica para crear una clase `CurrencyAmount` donde `T` representa el tipo de moneda, lo que permite cálculos con seguridad de tipos y el almacenamiento de diferentes cantidades de moneda.

4. Múltiples Parámetros de Tipo

Los genéricos pueden usar múltiples parámetros de tipo:


function swap(a: T, b: U): [U, T] {
  return [b, a];
}

let result = swap("hello", 42);
// result[0] es number, result[1] es string

La función swap toma dos argumentos de diferentes tipos y devuelve una tupla con los tipos intercambiados.

Relevancia Global: En aplicaciones comerciales internacionales, podrías tener una función que toma dos piezas de datos relacionadas con diferentes tipos y devuelve una tupla de ellas, como una ID de cliente (cadena) y el valor del pedido (número). Este patrón no favorece a ningún país específico y se adapta perfectamente a las necesidades globales.

5. Uso de Parámetros de Tipo en Restricciones Genéricas

Puedes usar un parámetro de tipo dentro de una restricción.


function getProperty(obj: T, key: K) {
  return obj[key];
}

let obj = { a: 1, b: 2, c: 3 };

let value = getProperty(obj, "a"); // value es number

En este ejemplo, K extends keyof T significa que K solo puede ser una clave del tipo T. Esto proporciona una gran seguridad de tipos al acceder a las propiedades del objeto dinámicamente.

Aplicabilidad Global: Esto es especialmente útil cuando se trabaja con objetos de configuración o estructuras de datos donde el acceso a la propiedad debe validarse durante el desarrollo. Esta técnica se puede aplicar en aplicaciones en cualquier país.

6. Tipos de Utilidad Genéricos

TypeScript proporciona varios tipos de utilidad integrados que utilizan genéricos para realizar transformaciones de tipo comunes. Estos incluyen:

Por ejemplo:


interface User {
  id: number;
  name: string;
  email: string;
}

// Partial - all properties optional
let optionalUser: Partial = {};

// Pick - only id and name properties
let userSummary: Pick = { id: 1, name: 'John' };

Caso de Uso Global: Estas utilidades son invaluables al crear modelos de solicitud y respuesta de API. Por ejemplo, en una aplicación de comercio electrónico global, Partial se puede usar para representar una solicitud de actualización (donde solo se envían algunos detalles del producto), mientras que Readonly podría representar un producto que se muestra en el frontend.

7. Inferencia de Tipo con Genéricos

TypeScript a menudo puede inferir los parámetros de tipo basándose en los argumentos que pasas a una función o clase genérica. Esto puede hacer que tu código sea más limpio y fácil de leer.


function createPair(a: T, b: T): [T, T] {
  return [a, b];
}

let pair = createPair("hello", "world"); // TypeScript infiere T como string

En este caso, TypeScript infiere automáticamente que T es string porque ambos argumentos son cadenas.

Impacto Global: La inferencia de tipo reduce la necesidad de anotaciones de tipo explícitas, lo que puede hacer que tu código sea más conciso y legible. Esto mejora la colaboración entre diversos equipos de desarrollo, donde pueden existir diferentes niveles de experiencia.

8. Tipos Condicionales con Genéricos

Los tipos condicionales, en conjunto con los genéricos, proporcionan una forma poderosa de crear tipos que dependen de los valores de otros tipos.


type Check = T extends string ? string : number;

let result1: Check = "hello"; // string
let result2: Check = 42; // number

En este ejemplo, Check se evalúa como string si T extiende string, de lo contrario, se evalúa como number.

Contexto Global: Los tipos condicionales son extremadamente útiles para dar forma dinámica a los tipos en función de ciertas condiciones. Imagina un sistema que procesa datos según la región. Los tipos condicionales se pueden usar para transformar datos en función de los formatos de datos o tipos de datos específicos de la región. Esto es crucial para aplicaciones con requisitos globales de gobernanza de datos.

9. Uso de Genéricos con Tipos Mapeados

Los tipos mapeados te permiten transformar las propiedades de un tipo en función de otro tipo. Combínalos con genéricos para mayor flexibilidad:


type OptionsFlags = {
  [K in keyof T]: boolean;
};

interface FeatureFlags {
  darkMode: boolean;
  notifications: boolean;
}

// Create a type where each feature flag is enabled (true) or disabled (false)
let featureFlags: OptionsFlags = {
  darkMode: true,
  notifications: false,
};

El tipo OptionsFlags toma un tipo genérico T y crea un nuevo tipo donde las propiedades de T ahora se asignan a valores booleanos. Esto es muy poderoso para trabajar con configuraciones o indicadores de características.

Aplicación Global: Este patrón permite crear esquemas de configuración basados en configuraciones específicas de la región. Este enfoque permite a los desarrolladores definir configuraciones específicas de la región (por ejemplo, los idiomas admitidos en una región). Permite la creación y el mantenimiento fáciles de esquemas de configuración de aplicaciones globales.

10. Inferencia Avanzada con la Palabra Clave `infer`

La palabra clave infer te permite extraer tipos de otros tipos dentro de los tipos condicionales.


type ReturnType any> = T extends (...args: any) => infer R ? R : any;

function myFunction(): string {
  return "hello";
}

let result: ReturnType = "hello"; // result is string

Este ejemplo infiere el tipo de retorno de una función usando la palabra clave infer. Esta es una técnica sofisticada para una manipulación de tipo más avanzada.

Significado Global: Esta técnica puede ser vital en grandes proyectos de software global distribuido para proporcionar seguridad de tipo mientras se trabaja con firmas de función complejas y estructuras de datos complejas. Permite generar tipos dinámicamente a partir de otros tipos, mejorando el mantenimiento del código.

Mejores Prácticas y Consejos

Conclusión: Abrazando el Poder de los Genéricos a Nivel Global

Los genéricos de TypeScript son una piedra angular para escribir código robusto y mantenible. Al dominar estos patrones avanzados, puedes mejorar significativamente la seguridad de tipos, la reutilización y la calidad general de tus aplicaciones JavaScript. Desde simples restricciones de tipo hasta tipos condicionales complejos, los genéricos proporcionan las herramientas que necesitas para crear software escalable y mantenible para una audiencia global. Recuerda que los principios del uso de genéricos siguen siendo coherentes independientemente de tu ubicación geográfica.

Al aplicar las técnicas discutidas en este artículo, puedes crear un código mejor estructurado, más confiable y fácilmente extensible, lo que en última instancia conduce a proyectos de software más exitosos, independientemente del país, continente o negocio en el que estés involucrado. ¡Adopta los genéricos y tu código te lo agradecerá!