TypeScript

TypeScript

Tutorial TypeScript: Tipos genéricos básicos

Aprende tipos genéricos en TypeScript para crear funciones flexibles, aplicar restricciones y manejar múltiples parámetros con seguridad de tipos.

Aprende TypeScript y certifícate

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.

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.

Aprende TypeScript online

Otros ejercicios de programación de TypeScript

Evalúa tus conocimientos de esta lección Tipos genéricos básicos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones

TypeScript
Test

Reto composición de funciones

TypeScript
Código

Reto tipos especiales

TypeScript
Código

Reto tipos genéricos

TypeScript
Código

Módulos

TypeScript
Test

Polimorfismo

TypeScript
Código

Funciones TypeScript

TypeScript
Código

Interfaces

TypeScript
Puzzle

Funciones puras

TypeScript
Puzzle

Reto namespaces

TypeScript
Código

Funciones flecha

TypeScript
Puzzle

Polimorfismo

TypeScript
Test

Operadores

TypeScript
Test

Conversor de unidades

TypeScript
Proyecto

Funciones flecha

TypeScript
Test

Control de flujo

TypeScript
Código

Herencia

TypeScript
Puzzle

Clases

TypeScript
Puzzle

Proyecto validación de tipado

TypeScript
Proyecto

Clases y objetos

TypeScript
Código

Encapsulación

TypeScript
Test

Herencia

TypeScript
Test

Proyecto sistema de votación

TypeScript
Proyecto

Reto genéricos con clases

TypeScript
Código

Inmutabilidad

TypeScript
Puzzle

Interfaces

TypeScript
Test

Funciones de alto orden

TypeScript
Test

Reto map y filter

TypeScript
Código

Control de flujo

TypeScript
Test

Interfaces

TypeScript
Código

Reto funciones orden superior

TypeScript
Código

Herencia y clases abstractas

TypeScript
Código

Reto tipos mapped

TypeScript
Código

Herencia de clases

TypeScript
Código

Reto funciones puras

TypeScript
Código

Variables y constantes

TypeScript
Puzzle

Introducción a TypeScript

TypeScript
Test

Reto testing unitario

TypeScript
Código

Funciones de primera clase

TypeScript
Puzzle

Clases

TypeScript
Test

OOP y CRUD en TypeScript

TypeScript
Proyecto

Interfaces y su implementación

TypeScript
Código

Tipos genéricos

TypeScript
Test

Namespaces

TypeScript
Test

Operadores y expresiones

TypeScript
Código

Proyecto generador de contraseñas

TypeScript
Proyecto

Reto unión e intersección

TypeScript
Código

Encapsulación

TypeScript
Puzzle

Tipos de unión e intersección

TypeScript
Test

Tipos de unión e intersección

TypeScript
Puzzle

Reto hola mundo en TS

TypeScript
Código

Variables y constantes

TypeScript
Código

Funciones puras

TypeScript
Test

Control de flujo

TypeScript
Código

Introducción a TypeScript

TypeScript
Código

Resolución de módulos

TypeScript
Test

Control de flujo

TypeScript
Puzzle

Reto tipos de utilidad

TypeScript
Código

Reto tipos literales y condicionales

TypeScript
Código

Reto exportar e importar

TypeScript
Código

Propiedades y métodos

TypeScript
Código

Tipos de utilidad

TypeScript
Test

Clases y objetos

TypeScript
Código

Tipos de datos, variables y constantes

TypeScript
Código

Proyecto Minigestor de tareas

TypeScript
Proyecto

Operadores

TypeScript
Puzzle

Funciones flecha y contexto

TypeScript
Código

Proyecto Inventario de productos

TypeScript
Proyecto

Funciones

TypeScript
Puzzle

Reto type aliases

TypeScript
Código

Funciones de alto orden

TypeScript
Puzzle

Funciones y parámetros tipados

TypeScript
Código

Tipos literales

TypeScript
Puzzle

Reto enums

TypeScript
Código

Tipos de utilidad

TypeScript
Puzzle

Modificadores de acceso y encapsulación

TypeScript
Código

Polimorfismo

TypeScript
Puzzle

Tipos genéricos

TypeScript
Puzzle

Reto módulos

TypeScript
Código

Tipos literales

TypeScript
Test

Inmutabilidad

TypeScript
Test

Proyecto Generator de datos

TypeScript
Proyecto

Variables y constantes

TypeScript
Test

Funciones de primera clase

TypeScript
Test

Todas las lecciones de TypeScript

Accede a todas las lecciones de TypeScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Typescript

TypeScript

Introducción Y Entorno

Instalación Y Configuración De Typescript

TypeScript

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

TypeScript

Sintaxis

Operadores Y Expresiones

TypeScript

Sintaxis

Control De Flujo

TypeScript

Sintaxis

Funciones Y Parámetros Tipados

TypeScript

Sintaxis

Funciones Flecha Y Contexto

TypeScript

Sintaxis

Enums

TypeScript

Sintaxis

Type Aliases Y Aserciones De Tipo

TypeScript

Sintaxis

Clases Y Objetos

TypeScript

Programación Orientada A Objetos

Interfaces Y Su Implementación

TypeScript

Programación Orientada A Objetos

Modificadores De Acceso Y Encapsulación

TypeScript

Programación Orientada A Objetos

Herencia Y Clases Abstractas

TypeScript

Programación Orientada A Objetos

Polimorfismo

TypeScript

Programación Orientada A Objetos

Decoradores Básicos

TypeScript

Programación Orientada A Objetos

Propiedades Y Métodos

TypeScript

Programación Orientada A Objetos

Inmutabilidad

TypeScript

Programación Funcional

Funciones Puras Y Efectos Secundarios

TypeScript

Programación Funcional

Funciones De Primera Clase

TypeScript

Programación Funcional

Funciones De Alto Orden

TypeScript

Programación Funcional

Conceptos Básicos E Inmutabilidad

TypeScript

Programación Funcional

Funciones De Primera Clase Y Orden Superior

TypeScript

Programación Funcional

Composición De Funciones

TypeScript

Programación Funcional

Métodos Funcionales De Arrays (Map, Filter, Reduce)

TypeScript

Programación Funcional

Tipos Literales Y Tipos Condicionales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Genéricos Básicos

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Unión E Intersección

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Utilidad (Partial, Required, Pick, Etc)

TypeScript

Tipos Intermedios Y Avanzados

Unknown, Never Y Tipos Especiales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Mapped

TypeScript

Tipos Intermedios Y Avanzados

Genéricos Con Clases E Interfaces

TypeScript

Tipos Intermedios Y Avanzados

Módulos

TypeScript

Namespaces Y Módulos

Namespaces

TypeScript

Namespaces Y Módulos

Resolución De Módulos

TypeScript

Namespaces Y Módulos

Exportación E Importación De Módulos

TypeScript

Namespaces Y Módulos

Introducción A Módulos

TypeScript

Namespaces Y Módulos

Testing Unitario En Typescript

TypeScript

Testing

Accede GRATIS a TypeScript y certifícate

En esta lección

Objetivos de aprendizaje 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.