Saltar al contenido principal

TypeScript Avanzado (Continuación) y Rendimiento

11. Reglas sobre Refinamiento de Tipos (Narrowing, Type Guards y Assertions)

Regla Principal

Regla:

  1. Narrowing Obligatorio: Con tipos unión (|) o unknown, usar técnicas de refinamiento (typeof, instanceof, in, type guards) para asegurar tipo específico antes de operar.
  2. Type Guards: Crear funciones type guard (param is Tipo) para lógica de comprobación compleja/reutilizable.
  3. Evitar Type Assertions: Evitar as Tipo o <Tipo>valor. Son último recurso inseguro (solo si se está 100% seguro y TS no infiere), justificar y encapsular si es posible.

Contexto y Justificación

Narrowing es esencial para seguridad con uniones/unknown. Type guards encapsulan comprobaciones. Assertions desactivan seguridad y ocultan errores.

Ejemplos y Contraejemplos

  • Correcto (Narrowing, Type Guard):
    function procesar(val: string | number) {
    if (typeof val === 'string') { console.log(val.toUpperCase()); } // typeof
    else { console.log(val.toFixed(2)); }
    }

    class Perro { ladrar() {} } class Gato { maullar() {} }
    function sonido(animal: Perro | Gato) {
    if (animal instanceof Perro) { animal.ladrar(); } // instanceof
    else { animal.maullar(); }
    }

    interface Admin { rol: 'admin'; } interface User { rol: 'user'; }
    function panel(p: Admin | User) {
    if ('rol' in p && p.rol === 'admin') { console.log('Admin'); } // in
    else { console.log('User'); }
    }

    interface Pez { nadar: () => void; } interface Ave { volar: () => void; }
    function esAve(a: Pez | Ave): a is Ave { return typeof (a as Ave).volar === 'function'; } // Type Guard
    function mover(a: Pez | Ave) {
    if (esAve(a)) { a.volar(); } else { a.nadar(); }
    }
  • Incorrecto (Sin narrowing, assertion insegura):
    function getLen(val: string | any[]) {
    // console.log(val.length); // INCORRECTO: Error TS o inseguro
    }

    interface Coche { marca: string; }
    function getMarca(v: unknown): string {
    const c = v as Coche; // INCORRECTO: Inseguro
    return c.marca; // Puede fallar en runtime
    }

Cuándo Aplicar

Obligatorio con unknown o uniones para acceder a miembros específicos. Type guards para lógica compleja/reutilizable.

Cuándo Evitar o Flexibilizar

Assertions: Evitar. Usar solo como último recurso absoluto justificado.

12. Reglas de Manejo Avanzado de Tipos

Regla Principal

Regla: Usar tipos avanzados de TS para modelos precisos/flexibles/seguros:

  1. Unión (|): Para variables con un conjunto limitado de tipos posibles.
  2. Intersección (&): Para combinar múltiples tipos en uno (requiere cumplir todas las props).
  3. Mapeados (Mapped Types): Para transformar tipos existentes (Partial, Readonly, Pick, Omit, Record, etc.). Preferir utility types incorporados antes que mapeados personalizados complejos.

Contexto y Justificación

Tipos avanzados modelan datos complejos de forma segura. Uniones = flexibilidad controlada. Intersecciones = composición. Mapeados = manipulación programática de tipos, evitan repetición.

Ejemplos y Contraejemplos

  • Correcto:
    type ID = string | number; // Unión
    function findById(id: ID) { /*...*/ }

    interface ConNombre { nombre: string; } interface ConEdad { edad: number; }
    type Persona = ConNombre & ConEdad; // Intersección
    const p: Persona = { nombre: 'Ana', edad: 30 };

    interface User { id: number; name: string; email?: string; }
    type UpdateDto = Partial<User>; // Mapeado (Utility)
    type UserBase = Pick<User, 'id' | 'name'>; // Mapeado (Utility)
    type FeatureFlags = Record<string, boolean>; // Mapeado (Utility)
  • Incorrecto:
    function findByIdAny(id: any) { /*...*/ } // INCORRECTO: any
    // Redefinir manualmente en lugar de Utility Types
    interface UpdateDtoManual { id?: number; name?: string; email?: string; } // INCORRECTO: Repetitivo

Cuándo Aplicar

Siempre que se necesite modelar datos con múltiples formas, combinar características o derivar tipos programáticamente.

Cuándo Evitar o Flexibilizar

Uniones: No usar con demasiados tipos. Intersecciones: Evitar complejidad excesiva. Mapeados: Usar utility types si existen; reservar personalizados para casos justificados.

13. Reglas sobre Decoradores

Regla Principal

Regla: Usar Decoradores (@decorador) con prudencia (son experimentales: experimentalDecorators).

  1. Uso Preferente: Priorizar decoradores de frameworks/librerías establecidos (NestJS, TypeORM, class-validator).
  2. Creación Personalizada: Reservada para lógica transversal clara (logging, acceso) donde otras opciones (composición) no sean tan adecuadas.
  3. Claridad: Propósito claro, no oscurecer lógica principal.
  4. Documentación: Decoradores personalizados deben documentarse.

Contexto y Justificación

Sintaxis elegante para metaprogramación. Uso indiscriminado dificulta depuración. Frameworks usan patrones probados. Creación personalizada requiere buen entendimiento.

Ejemplos y Contraejemplos

  • Correcto (Framework, Simple Personalizado):
    // NestJS (Framework)
    import { Controller, Get, Param, Injectable } from '@nestjs/common';
    @Injectable() class Srv {}
    @Controller('items') class Ctrl {
    constructor(private srv: Srv) {}
    @Get(':id') findOne(@Param('id') id: string) {}
    }

    // Simple Logging Decorator (Conceptual)
    function Log(target: any, key: string, desc: PropertyDescriptor) { /*...*/ }
    class Calc { @Log sumar(a: number, b: number) { return a + b; } }
  • Incorrecto (Complejo, Lógica de Negocio):
    // @MegaDecorador(...) // INCORRECTO: Mezcla responsabilidades
    class Proceso {
    // @ValidarNegocio // INCORRECTO: Lógica debe estar en métodos/servicios
    ejecutar() {}
    }

Cuándo Aplicar

Obligatorio con frameworks que los usan. Recomendado para lógica transversal clara y reutilizable.

Cuándo Evitar o Flexibilizar

Evitar si composición/funciones orden superior son más claras. No usar para lógica de negocio principal. Ser consciente de etapa experimental.

14. Reglas sobre Namespaces y Módulos Internos (TypeScript)

Regla Principal

Regla: Prohibido usar namespace (módulos internos) en código nuevo. Usar exclusivamente módulos ES6 (import/export).

Contexto y Justificación

namespace es obsoleto (pre-ES6). Módulos ES6 son el estándar robusto, explícito y compatible con ecosistema moderno (browsers, Node, bundlers). namespace dificulta integración y optimizaciones (tree-shaking).

Ejemplos y Contraejemplos

  • Correcto (Módulos ES6):
    // validacion/utils.ts
    export function esEmail(e: string): boolean { /*...*/ return true; }
    // app.ts
    import { esEmail } from './validacion/utils';
    console.log(esEmail('a@b.c'));
  • Incorrecto (namespace):
    // validacion-obsoleta.ts
    namespace Validacion { export function esEmail(e: string) { /*...*/ } } // INCORRECTO
    // app-obsoleta.ts
    // /// <reference path="validacion-obsoleta.ts" /> // INCORRECTO
    console.log(Validacion.esEmail('a@b.c'));

Cuándo Aplicar

Todo código TypeScript nuevo.

Cuándo Evitar o Flexibilizar

Nunca en código nuevo. Solo se encuentra en código legacy (refactorizar a ES6 a largo plazo).

15. Reglas de Buenas Prácticas de Rendimiento

Regla Principal

Regla: Seguir buenas prácticas generales de rendimiento:

  1. Bucles: Cachear valores/propiedades constantes fuera del bucle.
  2. DOM (Browser): Minimizar operaciones directas, agrupar cambios, usar DocumentFragments, delegar eventos.
  3. Estructuras Datos: Usar la adecuada (Map/Set para búsquedas rápidas vs Array).
  4. Minimizar Creación: Evitar crear objetos/funciones innecesarias en bucles/funciones frecuentes.
  5. Lazy Loading: Cargar código/recursos solo cuando se necesiten.
  6. Debounce/Throttle: Para eventos frecuentes (resize, scroll, input).

Contexto y Justificación

Evitar cuellos de botella innecesarios. IA debe ser consciente de operaciones costosas y favorecer alternativas eficientes sin sacrificar excesiva legibilidad.

Ejemplos y Contraejemplos

  • Correcto:
    // Cachear longitud
    const items = getItems(); const len = items.length;
    for (let i = 0; i < len; i++) { process(items[i]); }

    // Usar Set para búsqueda rápida
    const processedIds = new Set<number>();
    function needsProcessing(id: number): boolean { return !processedIds.has(id); } // O(1)

    // Debounce (Conceptual con lodash)
    import _ from 'lodash';
    const searchHandler = _.debounce((term) => { console.log(term); }, 300);
    input.addEventListener('input', (e) => searchHandler(e.target.value));
  • Incorrecto:
    for (let i = 0; i < items.length; i++) { /*...*/ } // Acceso repetido a .length

    const processedArr: number[] = [];
    function needsProcessingSlow(id: number): boolean { return !processedArr.includes(id); } // O(n)

    input.addEventListener('input', (e) => console.log(e.target.value)); // Sin debounce

Cuándo Aplicar

Considerar en todo código, especialmente bucles, colecciones grandes, DOM, eventos frecuentes.

Cuándo Evitar o Flexibilizar

Priorizar claridad/corrección sobre optimización prematura. Micro-optimizaciones raramente significativas. Equilibrar rendimiento y complejidad.