Tipos genéricos básicos

Avanzado
TypeScript
TypeScript
Actualizado: 09/05/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Sintaxis genérica <T>

Los tipos genéricos son una de las características más potentes de TypeScript, permitiendo crear componentes reutilizables que pueden trabajar con una variedad de tipos en lugar de un único tipo específico. La sintaxis genérica se identifica mediante el uso de los corchetes angulares <> y un parámetro de tipo que convencionalmente se representa con la letra T (aunque puede utilizarse cualquier identificador válido).

La idea fundamental detrás de los genéricos es permitir que especifiques un contrato entre el tipo de datos que proporcionas y el que esperas recibir, manteniendo la seguridad de tipos durante todo el proceso.

Declaración básica de genéricos

La sintaxis básica para declarar un tipo genérico utiliza los corchetes angulares <T> donde T actúa como un parámetro de tipo:

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

En este ejemplo, la función identity acepta un argumento de cualquier tipo y devuelve un valor del mismo tipo. El parámetro T captura el tipo proporcionado, permitiendo usarlo en múltiples lugares dentro de la función.

Uso de tipos genéricos

Existen dos formas principales de invocar una función genérica:

// 1. Especificando explícitamente el tipo
let output1 = identity<string>("Hello, TypeScript");

// 2. Usando inferencia de tipos (más común)
let output2 = identity("Hello, TypeScript"); // TypeScript infiere que T es string

La segunda forma es más concisa y generalmente preferida, ya que TypeScript puede inferir el tipo automáticamente basándose en el argumento proporcionado.

Genéricos con interfaces

Los genéricos también pueden aplicarse a interfaces para crear estructuras de datos flexibles:

interface Box<T> {
    value: T;
}

// Uso con un tipo específico
let stringBox: Box<string> = { value: "TypeScript" };
let numberBox: Box<number> = { value: 42 };

Esta interfaz Box puede contener cualquier tipo de valor, manteniendo la información de tipo durante todo el código.

Genéricos con clases

De manera similar, podemos crear clases genéricas:

class Container<T> {
    private item: T;
    
    constructor(item: T) {
        this.item = item;
    }
    
    getItem(): T {
        return this.item;
    }
}

// Instanciación con diferentes tipos
const numberContainer = new Container<number>(123);
const stringContainer = new Container("Hello");  // Inferencia de tipos

Genéricos con tipos y alias

Los genéricos también funcionan con alias de tipos:

type Pair<T, U> = {
    first: T;
    second: U;
};

// Uso del alias genérico
const coordinates: Pair<number, number> = { first: 10, second: 20 };
const entry: Pair<string, boolean> = { first: "isActive", second: true };

Parámetros de tipo por defecto

TypeScript permite especificar valores por defecto para los parámetros de tipo:

interface ApiResponse<T = any> {
    data: T;
    status: number;
    message: string;
}

// Sin especificar el tipo, usa el valor por defecto (any)
const genericResponse: ApiResponse = {
    data: { whatever: "anything" },
    status: 200,
    message: "OK"
};

// Especificando un tipo concreto
interface User {
    id: number;
    name: string;
}

const userResponse: ApiResponse<User> = {
    data: { id: 1, name: "John" },
    status: 200,
    message: "User found"
};

Convenciones de nomenclatura

Aunque T es la convención más común para un parámetro de tipo genérico, existen otras convenciones útiles:

// T para Tipo general
function process<T>(x: T): T { return x; }

// K para Key (Clave), V para Value (Valor)
function getProperty<K extends keyof T, T>(obj: T, key: K): T[K] {
    return obj[key];
}

// E para Element (Elemento en colecciones)
function firstElement<E>(arr: E[]): E | undefined {
    return arr[0];
}

Estas convenciones ayudan a que el código sea más legible y expresivo, especialmente cuando se utilizan múltiples parámetros de tipo.

Genéricos con arreglos y colecciones

Los genéricos son particularmente útiles para trabajar con colecciones:

function getFirstElement<T>(array: T[]): T | undefined {
    return array.length > 0 ? array[0] : undefined;
}

const numbers = [1, 2, 3, 4, 5];
const firstNumber = getFirstElement(numbers); // Tipo: number | undefined

const names = ["Alice", "Bob", "Charlie"];
const firstName = getFirstElement(names); // Tipo: string | undefined

Ventajas de usar genéricos

Los genéricos ofrecen varias ventajas importantes:

  • Reutilización de código: Escribe una función o clase una vez y úsala con diferentes tipos.
  • Seguridad de tipos: Mantiene la información de tipo en toda la aplicación.
  • Flexibilidad: Permite crear componentes que se adaptan a diferentes contextos.
  • Autocompletado: El editor puede sugerir propiedades y métodos específicos del tipo.

La sintaxis genérica <T> es el fundamento para crear código TypeScript flexible y reutilizable, permitiendo escribir componentes que funcionan con una variedad de tipos mientras se mantiene la seguridad de tipos estáticos.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Funciones genéricas

Las funciones genéricas representan una de las aplicaciones más prácticas y comunes de los genéricos en TypeScript. Estas funciones nos permiten escribir código reutilizable que puede operar con diferentes tipos de datos mientras mantiene la seguridad de tipos en tiempo de compilación.

Estructura básica de una función genérica

Una función genérica se define añadiendo un parámetro de tipo entre corchetes angulares antes de la lista de parámetros de la función:

function nombreFuncion<T>(parametro: T): T {
    // Implementación
    return parametro;
}

El parámetro de tipo T actúa como un marcador de posición que será reemplazado por un tipo real cuando se invoque la función.

Casos de uso prácticos

Las funciones genéricas son especialmente útiles cuando necesitamos mantener la información de tipo a través de operaciones:

function reverseArray<T>(items: T[]): T[] {
    return [...items].reverse();
}

// Uso con diferentes tipos
const numbers = reverseArray([1, 2, 3, 4, 5]); // tipo: number[]
const names = reverseArray(["Ana", "Carlos", "Berta"]); // tipo: string[]
const mixed = reverseArray([true, 42, "hola"]); // tipo: (string | number | boolean)[]

En este ejemplo, la función reverseArray preserva el tipo de los elementos del array, independientemente de qué tipo sea.

Múltiples parámetros de tipo

Podemos definir funciones genéricas con varios parámetros de tipo cuando necesitamos trabajar con diferentes tipos en la misma función:

function merge<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const result = merge(
    { name: "Elena" }, 
    { age: 28 }
); // tipo: { name: string; age: number; }

console.log(result.name); // "Elena"
console.log(result.age);  // 28

Esta función combina dos objetos y TypeScript infiere correctamente que el resultado tendrá las propiedades de ambos objetos.

Inferencia de tipos en funciones genéricas

TypeScript puede inferir automáticamente los tipos genéricos basándose en los argumentos proporcionados:

function wrapInArray<T>(value: T): T[] {
    return [value];
}

// Inferencia automática
const stringArray = wrapInArray("TypeScript"); // tipo: string[]
const numberArray = wrapInArray(42);           // tipo: number[]

Aunque también podemos especificar explícitamente el tipo:

const explicitArray = wrapInArray<boolean>(true); // tipo: boolean[]

Funciones genéricas con tipos de retorno específicos

Podemos definir funciones genéricas donde el tipo de retorno sea diferente del tipo de entrada:

function convertToString<T>(value: T): string {
    return String(value);
}

const numAsString = convertToString(123); // tipo: string

Funciones genéricas con arrow functions

Las funciones flecha también pueden ser genéricas:

const getFirstItem = <T>(array: T[]): T | undefined => {
    return array.length > 0 ? array[0] : undefined;
};

const first = getFirstItem([10, 20, 30]); // tipo: number | undefined

Nota: En JSX, puede ser necesario añadir una coma después del parámetro de tipo para evitar ambigüedades con las etiquetas:

const getFirstItem = <T,>(array: T[]): T | undefined => {
    return array.length > 0 ? array[0] : undefined;
};

Funciones genéricas como parámetros

Podemos pasar funciones genéricas como parámetros a otras funciones:

function executeOperation<T, U>(
    data: T, 
    operation: (x: T) => U
): U {
    return operation(data);
}

const lengthResult = executeOperation("hello", (x) => x.length); // tipo: number
const doubleResult = executeOperation(5, (x) => x * 2);          // tipo: number

Funciones genéricas con tipos condicionales

Podemos combinar funciones genéricas con tipos condicionales para crear comportamientos más sofisticados:

function process<T>(value: T): T extends string ? string : number {
    if (typeof value === "string") {
        return value.toUpperCase() as any;
    } else {
        return (typeof value === "number" ? value * 2 : 0) as any;
    }
}

const stringResult = process("hello"); // tipo: string
const numberResult = process(10);      // tipo: number

Funciones genéricas con promesas

Las funciones genéricas son especialmente útiles cuando trabajamos con promesas:

async function fetchData<T>(url: string): Promise<T> {
    const response = await fetch(url);
    return response.json();
}

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

// El tipo de retorno será Promise<User[]>
const users = await fetchData<User[]>('/api/users');
// TypeScript sabe que users es de tipo User[]
users.forEach(user => console.log(user.name));

Funciones genéricas con restricciones

Aunque las restricciones se verán con más detalle en otra sección, es importante mencionar que podemos limitar los tipos que pueden usarse con una función genérica:

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

const user = { id: 1, name: "Sara", role: "Admin" };
const userName = getProperty(user, "name"); // tipo: string
// getProperty(user, "age"); // Error: 'age' no existe en el objeto

Buenas prácticas para funciones genéricas

  • Nombres descriptivos: Usa nombres de parámetros de tipo que reflejen su propósito (TInput, TOutput, etc.) cuando T no sea suficientemente descriptivo.
  • Inferencia cuando sea posible: Permite que TypeScript infiera los tipos automáticamente para hacer el código más limpio.
  • Restricciones apropiadas: Usa restricciones para evitar errores en tiempo de ejecución.
  • Documentación: Documenta el propósito y los requisitos de tus funciones genéricas, especialmente si tienen comportamientos complejos.

Las funciones genéricas son una herramienta fundamental en TypeScript que nos permite escribir código más flexible y reutilizable sin sacrificar la seguridad de tipos, lo que resulta en aplicaciones más robustas y mantenibles.

Restricciones con extends

Cuando trabajamos con tipos genéricos en TypeScript, a veces necesitamos limitar los tipos que pueden utilizarse como argumentos genéricos. Las restricciones nos permiten especificar que un tipo debe cumplir ciertos requisitos, lo que nos ayuda a crear código más seguro y predecible.

La palabra clave extends es la que nos permite definir estas restricciones, estableciendo una relación de "es un subtipo de" o "cumple con la interfaz de".

Sintaxis básica de restricciones

La sintaxis para aplicar restricciones utiliza la palabra clave extends después del parámetro de tipo:

function ejemplo<T extends TipoBase>(arg: T): T {
    // Implementación
    return arg;
}

Donde TipoBase representa el tipo mínimo que debe cumplir T.

Restricciones con interfaces

Una de las aplicaciones más comunes es restringir un tipo genérico para que cumpla con una interfaz específica:

interface ConNombre {
    nombre: string;
}

function imprimirNombre<T extends ConNombre>(objeto: T): void {
    console.log(objeto.nombre);
}

// Funciona porque el objeto tiene la propiedad 'nombre'
imprimirNombre({ nombre: "Ana", edad: 30 });

// Error: el argumento no tiene la propiedad 'nombre'
// imprimirNombre({ edad: 25 });

En este ejemplo, la función imprimirNombre solo acepta tipos que tengan al menos una propiedad nombre de tipo string.

Restricciones con tipos primitivos

También podemos restringir los tipos genéricos a ciertos tipos primitivos:

function convertirAString<T extends string | number | boolean>(valor: T): string {
    return String(valor);
}

const resultado1 = convertirAString("hola");    // Funciona
const resultado2 = convertirAString(42);        // Funciona
const resultado3 = convertirAString(true);      // Funciona

// Error: el tipo 'object' no satisface la restricción
// const resultado4 = convertirAString({});

Restricciones con uniones de tipos

Las restricciones pueden utilizar uniones de tipos para permitir múltiples tipos base:

type Numerico = number | bigint;

function duplicar<T extends Numerico>(valor: T): T {
    // Usamos 'as T' porque TypeScript no puede inferir que el resultado
    // sigue siendo del mismo tipo específico
    return (valor + valor) as T;
}

const num = duplicar(5);        // tipo: number
const bigNum = duplicar(10n);   // tipo: bigint

Restricciones con tipos genéricos anidados

Podemos aplicar restricciones a tipos genéricos que a su vez contienen otros genéricos:

function procesarArray<T extends Array<string | number>>(items: T): T {
    console.log(`Procesando ${items.length} elementos`);
    return items;
}

const strArray = procesarArray(["a", "b", "c"]);           // Funciona
const numArray = procesarArray([1, 2, 3]);                 // Funciona
const mixedArray = procesarArray(["a", 1, "b", 2]);        // Funciona

// Error: el tipo 'boolean[]' no satisface la restricción
// const boolArray = procesarArray([true, false]);

Restricciones con keyof

El operador keyof combinado con restricciones genéricas nos permite crear funciones que operan de forma segura con las propiedades de un objeto:

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

const usuario = {
    id: 123,
    nombre: "Carlos",
    email: "carlos@ejemplo.com"
};

const id = obtenerPropiedad(usuario, "id");         // tipo: number
const nombre = obtenerPropiedad(usuario, "nombre"); // tipo: string

// Error: el argumento de tipo '"edad"' no es asignable al parámetro de tipo 'keyof...'
// const edad = obtenerPropiedad(usuario, "edad");

Esta técnica es extremadamente útil para crear funciones que acceden a propiedades de objetos de forma segura.

Restricciones con clases

Podemos restringir tipos genéricos para que sean instancias de una clase específica:

class Animal {
    nombre: string;
    
    constructor(nombre: string) {
        this.nombre = nombre;
    }
    
    hacerSonido(): void {
        console.log("Algún sonido");
    }
}

class Perro extends Animal {
    raza: string;
    
    constructor(nombre: string, raza: string) {
        super(nombre);
        this.raza = raza;
    }
    
    hacerSonido(): void {
        console.log("Guau guau");
    }
}

function crearAnimal<T extends Animal>(Constructor: new (...args: any[]) => T, nombre: string): T {
    return new Constructor(nombre);
}

const miPerro = crearAnimal(Perro, "Rex");  // tipo: Perro
miPerro.hacerSonido();  // "Guau guau"

Restricciones con tipos condicionales

Las restricciones pueden combinarse con tipos condicionales para crear comportamientos más sofisticados:

type EsArray<T> = T extends any[] ? true : false;

function procesarEntrada<T, R extends EsArray<T> extends true ? T : T[]>(
    entrada: T, 
    comoArray: R
): void {
    const array = comoArray ? (entrada as any) : [entrada];
    console.log(`Procesando ${array.length} elementos`);
}

Restricciones con genéricos por defecto

Podemos combinar restricciones con valores por defecto para los parámetros de tipo:

interface OpcionesBase {
    timeout?: number;
    cache?: boolean;
}

function configurar<T extends OpcionesBase = OpcionesBase>(opciones: T): void {
    console.log(`Timeout: ${opciones.timeout ?? 1000}ms`);
    console.log(`Cache: ${opciones.cache ?? true}`);
}

// Funciona con las opciones mínimas
configurar({});

// Funciona con opciones adicionales
configurar({ timeout: 2000, cache: false, debug: true });

Restricciones con tipos recursivos

Las restricciones también pueden aplicarse a tipos recursivos:

type ElementoAnidado<T> = T | Array<ElementoAnidado<T>>;

function aplanar<T, U extends ElementoAnidado<T>>(entrada: U): T[] {
    if (Array.isArray(entrada)) {
        return entrada.flatMap(item => aplanar(item));
    } else {
        return [entrada as T];
    }
}

const resultado = aplanar([1, [2, [3, 4]], 5]);  // [1, 2, 3, 4, 5]

Beneficios de usar restricciones

Las restricciones con extends ofrecen varios beneficios importantes:

  • Seguridad de tipos: Evitan errores en tiempo de compilación al garantizar que los tipos cumplen con ciertos requisitos.
  • Mejor autocompletado: El editor puede sugerir propiedades y métodos disponibles basados en las restricciones.
  • Código más expresivo: Las restricciones documentan implícitamente los requisitos de tus funciones y tipos.
  • Detección temprana de errores: Los problemas se detectan durante la compilación en lugar de en tiempo de ejecución.

Consideraciones al usar restricciones

  • Balance entre flexibilidad y seguridad: Restricciones demasiado estrictas pueden limitar la reutilización, mientras que restricciones demasiado laxas pueden reducir la seguridad.
  • Legibilidad: Las restricciones complejas pueden hacer que el código sea más difícil de entender.
  • Rendimiento de compilación: Las restricciones muy complejas pueden aumentar el tiempo de compilación.

Las restricciones con extends son una herramienta fundamental para crear APIs genéricas robustas en TypeScript, permitiéndonos encontrar un equilibrio entre flexibilidad y seguridad de tipos.

Múltiples genéricos

Los genéricos en TypeScript se vuelven aún más potentes cuando utilizamos múltiples parámetros de tipo en una misma estructura. Esta técnica nos permite crear componentes altamente flexibles que pueden manejar diferentes tipos de datos simultáneamente, manteniendo la seguridad de tipos en todo momento.

Sintaxis básica con múltiples parámetros

La sintaxis para declarar múltiples parámetros de tipo es sencilla: simplemente separamos cada parámetro con una coma dentro de los corchetes angulares:

function ejemplo<T, U>(primerArg: T, segundoArg: U): [T, U] {
    return [primerArg, segundoArg];
}

En este ejemplo, T y U son parámetros de tipo independientes que pueden representar cualquier tipo.

Casos de uso prácticos

Funciones con múltiples tipos

Una aplicación común es crear funciones que procesen diferentes tipos de entrada y produzcan resultados tipados correctamente:

function combinar<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const persona = { nombre: "Laura" };
const datos = { edad: 29, ciudad: "Barcelona" };

const resultado = combinar(persona, datos);
// resultado tiene tipo: { nombre: string; edad: number; ciudad: string; }

console.log(resultado.nombre); // "Laura"
console.log(resultado.edad);   // 29

Mapeo entre tipos diferentes

Los múltiples genéricos son ideales para transformar datos de un tipo a otro:

function transformar<T, U>(
    items: T[], 
    transformador: (item: T) => U
): U[] {
    return items.map(transformador);
}

const numeros = [1, 2, 3, 4, 5];
const numerosDuplicados = transformar(numeros, n => n * 2);
// numerosDuplicados: number[]

const numerosComoTexto = transformar(numeros, n => n.toString());
// numerosComoTexto: string[]

Interfaces con múltiples genéricos

Las interfaces también pueden definirse con varios parámetros de tipo:

interface Par<K, V> {
    clave: K;
    valor: V;
}

const coordenada: Par<string, number> = { clave: "latitud", valor: 41.3851 };
const configuracion: Par<string, boolean> = { clave: "habilitado", valor: true };

Esta técnica es especialmente útil para crear estructuras de datos como diccionarios o mapas:

interface Diccionario<K extends string | number | symbol, V> {
    [clave: K]: V;
    obtener(clave: K): V | undefined;
    establecer(clave: K, valor: V): void;
}

Clases con múltiples genéricos

Las clases pueden aprovechar múltiples genéricos para crear estructuras de datos versátiles:

class Coleccion<T, U> {
    private items: Array<[T, U]> = [];
    
    agregar(clave: T, valor: U): void {
        this.items.push([clave, valor]);
    }
    
    obtenerPorClave(clave: T): U | undefined {
        const par = this.items.find(item => item[0] === clave);
        return par ? par[1] : undefined;
    }
}

// Uso con diferentes combinaciones de tipos
const usuarios = new Coleccion<number, string>();
usuarios.agregar(1, "Carlos");
usuarios.agregar(2, "Ana");

const nombreUsuario = usuarios.obtenerPorClave(1); // tipo: string | undefined

Tipos alias con múltiples genéricos

Los alias de tipo también pueden utilizar múltiples parámetros:

type Resultado<T, E> = {
    exito: true;
    valor: T;
} | {
    exito: false;
    error: E;
};

function dividir(a: number, b: number): Resultado<number, string> {
    if (b === 0) {
        return { exito: false, error: "No se puede dividir por cero" };
    }
    return { exito: true, valor: a / b };
}

const resultado = dividir(10, 2);

if (resultado.exito) {
    console.log(`El resultado es: ${resultado.valor}`);
} else {
    console.log(`Error: ${resultado.error}`);
}

Este patrón es muy útil para manejar operaciones que pueden fallar, similar al tipo Result en lenguajes como Rust.

Genéricos anidados

Podemos combinar múltiples genéricos con estructuras anidadas para crear tipos más complejos:

interface Respuesta<T, M extends { [key: string]: any }> {
    datos: T;
    metadata: M;
    timestamp: number;
}

type PaginacionMetadata = {
    pagina: number;
    total: number;
    porPagina: number;
};

// Uso con tipos específicos
const respuestaPaginada: Respuesta<string[], PaginacionMetadata> = {
    datos: ["item1", "item2", "item3"],
    metadata: {
        pagina: 1,
        total: 100,
        porPagina: 10
    },
    timestamp: Date.now()
};

Funciones de orden superior con múltiples genéricos

Los múltiples genéricos son especialmente útiles en funciones de orden superior:

function componer<A, B, C>(
    f: (b: B) => C,
    g: (a: A) => B
): (a: A) => C {
    return (a) => f(g(a));
}

const convertirANumero = (str: string): number => parseInt(str, 10);
const duplicar = (num: number): number => num * 2;

const convertirYDuplicar = componer(duplicar, convertirANumero);

const resultado = convertirYDuplicar("10"); // 20

Promesas con múltiples genéricos

Cuando trabajamos con operaciones asíncronas, los múltiples genéricos nos permiten tipar correctamente diferentes escenarios:

interface ResultadoAsincrono<T, E = Error> {
    datos?: T;
    error?: E;
    estado: 'pendiente' | 'completado' | 'error';
}

async function obtenerDatos<T, E>(
    url: string
): Promise<ResultadoAsincrono<T, E>> {
    try {
        const respuesta = await fetch(url);
        if (!respuesta.ok) {
            throw await respuesta.json();
        }
        const datos = await respuesta.json();
        return { datos, estado: 'completado' };
    } catch (error) {
        return { 
            error: error as E, 
            estado: 'error' 
        };
    }
}

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

interface ErrorApi {
    codigo: number;
    mensaje: string;
}

// Uso con tipos específicos
const resultado = await obtenerDatos<Usuario, ErrorApi>('/api/usuarios/1');

if (resultado.estado === 'completado' && resultado.datos) {
    console.log(`Usuario: ${resultado.datos.nombre}`);
} else if (resultado.error) {
    console.error(`Error ${resultado.error.codigo}: ${resultado.error.mensaje}`);
}

Convenciones de nomenclatura

Cuando trabajamos con múltiples genéricos, es importante usar nombres descriptivos para mejorar la legibilidad:

// Convenciones comunes para múltiples genéricos
function procesar<TInput, TOutput>(entrada: TInput, fn: (data: TInput) => TOutput): TOutput {
    return fn(entrada);
}

// Para pares clave-valor
interface Mapa<K, V> {
    obtener(clave: K): V | undefined;
    establecer(clave: K, valor: V): void;
}

// Para contenedores
class Contenedor<TContenido, TMetadata> {
    contenido: TContenido;
    metadata: TMetadata;
    
    constructor(contenido: TContenido, metadata: TMetadata) {
        this.contenido = contenido;
        this.metadata = metadata;
    }
}

Restricciones con múltiples genéricos

Podemos aplicar restricciones diferentes a cada parámetro de tipo:

function fusionar<
    T extends object,
    U extends { id: number }
>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const resultado = fusionar(
    { nombre: "Producto A" },
    { id: 123, precio: 29.99 }
);
// resultado: { nombre: string; id: number; precio: number; }

También podemos crear restricciones donde un parámetro genérico dependa de otro:

function seleccionarPropiedades<
    T extends object,
    K extends keyof T
>(obj: T, propiedades: K[]): Pick<T, K> {
    const resultado = {} as Pick<T, K>;
    
    propiedades.forEach(prop => {
        resultado[prop] = obj[prop];
    });
    
    return resultado;
}

const usuario = {
    id: 1,
    nombre: "Elena",
    email: "elena@ejemplo.com",
    rol: "admin"
};

const credenciales = seleccionarPropiedades(usuario, ["id", "email"]);
// credenciales: { id: number; email: string; }

Buenas prácticas

Al trabajar con múltiples genéricos, es recomendable seguir estas pautas:

  • Limitar el número de parámetros: Usar demasiados parámetros genéricos puede hacer que el código sea difícil de entender. Intenta no exceder de 2-3 parámetros.

  • Nombres descriptivos: Usa nombres que reflejen el propósito de cada parámetro genérico (TInput, TOutput, TKey, TValue, etc.).

  • Valores por defecto: Considera proporcionar valores por defecto para parámetros genéricos que tengan casos de uso comunes.

  • Documentación: Documenta claramente el propósito de cada parámetro genérico, especialmente en APIs públicas.

Los múltiples genéricos son una herramienta fundamental para crear código TypeScript flexible y reutilizable que puede adaptarse a diferentes contextos mientras mantiene la seguridad de tipos, lo que resulta en aplicaciones más robustas y mantenibles.

Aprendizajes de esta lección

  • Comprender la sintaxis básica y el uso de tipos genéricos en funciones, interfaces y clases.
  • Aplicar funciones genéricas para mantener la seguridad de tipos y reutilización de código.
  • Implementar restricciones con extends para limitar los tipos genéricos permitidos.
  • Utilizar múltiples parámetros genéricos para crear estructuras y funciones más flexibles.
  • Seguir buenas prácticas y convenciones para mejorar la legibilidad y mantenimiento del código genérico.

Completa TypeScript y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración