TypeScript Avanzado (Continuación) y Rendimiento
11. Reglas sobre Refinamiento de Tipos (Narrowing, Type Guards y Assertions)
Regla Principal
Regla:
- Narrowing Obligatorio: Con tipos unión (
|) ounknown, usar técnicas de refinamiento (typeof,instanceof,in, type guards) para asegurar tipo específico antes de operar.- Type Guards: Crear funciones type guard (
param is Tipo) para lógica de comprobación compleja/reutilizable.- Evitar Type Assertions: Evitar
as Tipoo<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
unknowno 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:
- Unión (
|): Para variables con un conjunto limitado de tipos posibles.- Intersección (
&): Para combinar múltiples tipos en uno (requiere cumplir todas las props).- 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).
- Uso Preferente: Priorizar decoradores de frameworks/librerías establecidos (NestJS, TypeORM, class-validator).
- Creación Personalizada: Reservada para lógica transversal clara (logging, acceso) donde otras opciones (composición) no sean tan adecuadas.
- Claridad: Propósito claro, no oscurecer lógica principal.
- 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
namespacees obsoleto (pre-ES6). Módulos ES6 son el estándar robusto, explícito y compatible con ecosistema moderno (browsers, Node, bundlers).namespacedificulta 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:
- Bucles: Cachear valores/propiedades constantes fuera del bucle.
- DOM (Browser): Minimizar operaciones directas, agrupar cambios, usar DocumentFragments, delegar eventos.
- Estructuras Datos: Usar la adecuada (
Map/Setpara búsquedas rápidas vsArray).- Minimizar Creación: Evitar crear objetos/funciones innecesarias en bucles/funciones frecuentes.
- Lazy Loading: Cargar código/recursos solo cuando se necesiten.
- 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.