La optimización del rendimiento es crucial en el desarrollo de aplicaciones modernas. Entre las técnicas de optimización, la memoización destaca como una solución elegante y eficaz para mejorar significativamente el rendimiento de tus aplicaciones TypeScript. En esta guía completa, descubre cómo utilizar esta poderosa técnica para reducir el tiempo de ejecución de tus funciones hasta un 90%. 🚀
Entendiendo la Memoización en TypeScript
La memoización es una técnica de optimización que consiste en almacenar en caché los resultados de las llamadas a funciones. En lugar de recalcular el mismo resultado varias veces, lo almacenamos en memoria para su reutilización posterior. Este enfoque es particularmente efectivo para:
- Funciones con cálculos intensivos
- Operaciones recursivas
- Llamadas a API
- Transformaciones de datos complejas
Implementación Type-Safe de la Memoización
Comencemos definiendo tipos precisos y una implementación segura:
typescript
1// Tipos para la función de memoización
2type AnyFunction = (...args: unknown[]) => unknown;
3type MemoizedFunction<T extends AnyFunction> = T & {
4 clearCache: () => void;
5};
6
7// Tipo para las entradas de caché
8interface CacheEntry<T> {
9 value: T;
10 timestamp: number;
11}
12
13// Función de memoización básica con tipado estricto
14function memoize<T extends AnyFunction>(fn: T): MemoizedFunction<T> {
15 const cache = new Map<string, CacheEntry<ReturnType<T>>>();
16
17 const memoized = (...args: Parameters<T>): ReturnType<T> => {
18 const key = JSON.stringify(args);
19 const cached = cache.get(key);
20
21 if (cached) {
22 console.log(`Cache encontrado para la clave: ${key}`);
23 return cached.value;
24 }
25
26 const result = fn(...args);
27 cache.set(key, {
28 value: result,
29 timestamp: Date.now(),
30 });
31 console.log(`Nuevo cálculo para la clave: ${key}`);
32 return result;
33 };
34
35 // Agregar un método para limpiar el caché
36 const memoizedWithClear = memoized as MemoizedFunction<T>;
37 memoizedWithClear.clearCache = () => cache.clear();
38
39 return memoizedWithClear;
40}
Implementación Avanzada con Configuración Tipada
typescript
1// Tipos para las opciones de configuración
2interface MemoizeOptions {
3 maxCacheSize: number;
4 ttl: number; // Time To Live en milisegundos
5 cacheKeyGenerator?: <T extends unknown[]>(...args: T) => string;
6}
7
8// Tipo para las estadísticas de caché
9interface CacheStats {
10 hits: number;
11 misses: number;
12 size: number;
13 averageAccessTime: number;
14}
15
16// Implementación avanzada con gestión completa del caché
17function advancedMemoize<T extends AnyFunction>(
18 fn: T,
19 options: Partial<MemoizeOptions> = {},
20): MemoizedFunction<T> & { getStats: () => CacheStats } {
21 const {
22 maxCacheSize = 1000,
23 ttl = Infinity,
24 cacheKeyGenerator = JSON.stringify,
25 } = options;
26
27 const cache = new Map<string, CacheEntry<ReturnType<T>>>();
28 const stats = {
29 hits: 0,
30 misses: 0,
31 totalAccessTime: 0,
32 accessCount: 0,
33 };
34
35 const memoized = (...args: Parameters<T>): ReturnType<T> => {
36 const startTime = performance.now();
37 const key = cacheKeyGenerator(args);
38 const now = Date.now();
39 const cached = cache.get(key);
40
41 const updateStats = (hit: boolean): void => {
42 const accessTime = performance.now() - startTime;
43 stats.totalAccessTime += accessTime;
44 stats.accessCount += 1;
45 if (hit) stats.hits += 1;
46 else stats.misses += 1;
47 };
48
49 // Verificación de la validez del caché
50 if (cached && now - cached.timestamp <= ttl) {
51 updateStats(true);
52 return cached.value;
53 }
54
55 // Eliminación de entradas expiradas si es necesario
56 if (cached) {
57 cache.delete(key);
58 }
59
60 // Gestión del tamaño del caché
61 if (cache.size >= maxCacheSize) {
62 const oldestKey = cache.keys().next().value;
63 cache.delete(oldestKey);
64 }
65
66 const result = fn(...args);
67 cache.set(key, { value: result, timestamp: now });
68 updateStats(false);
69 return result;
70 };
71
72 // Agregar métodos utilitarios
73 const enhanced = memoized as MemoizedFunction<T> & {
74 getStats: () => CacheStats;
75 };
76 enhanced.clearCache = () => cache.clear();
77 enhanced.getStats = () => ({
78 hits: stats.hits,
79 misses: stats.misses,
80 size: cache.size,
81 averageAccessTime:
82 stats.accessCount > 0 ? stats.totalAccessTime / stats.accessCount : 0,
83 });
84
85 return enhanced;
86}
Ejemplos Prácticos Type-Safe
1. Cálculo de la Secuencia de Fibonacci con Tipos Estrictos
typescript
1type FibonacciFunction = (n: number) => number;
2
3const fibMemoized = memoize<FibonacciFunction>((n) => {
4 if (n <= 1) return n;
5 return fibMemoized(n - 1) + fibMemoized(n - 2);
6});
2. Peticiones API Tipadas
typescript
1interface User {
2 id: string;
3 name: string;
4 email: string;
5}
6
7type FetchUserFunction = (userId: string) => Promise<User>;
8
9const fetchUserDataMemoized = advancedMemoize<FetchUserFunction>(
10 async (userId) => {
11 const response = await fetch(`/api/users/${userId}`);
12 if (!response.ok) {
13 throw new Error(`Error al obtener usuario: ${response.statusText}`);
14 }
15 return response.json();
16 },
17 {
18 ttl: 300000, // Caché de 5 minutos
19 maxCacheSize: 100,
20 cacheKeyGenerator: (args) => args[0], // Usa directamente el ID como clave
21 },
22);
3. Transformación de Datos con Validación
typescript
1interface DataTransformOptions {
2 format: 'json' | 'xml';
3 version: number;
4}
5
6type TransformFunction = (
7 data: Record<string, unknown>,
8 options: DataTransformOptions,
9) => string;
10
11const transformDataMemoized = advancedMemoize<TransformFunction>(
12 (data, options) => {
13 // Lógica de transformación...
14 return JSON.stringify(data);
15 },
16 {
17 cacheKeyGenerator: (args) => {
18 const [data, options] = args;
19 return `${JSON.stringify(data)}-${options.format}-${options.version}`;
20 },
21 },
22);
Monitoreo Type-Safe del Rendimiento
typescript
1type MetricsWrapper<T extends AnyFunction> = T & {
2 getMetrics: () => {
3 totalCalls: number;
4 averageExecutionTime: number;
5 cacheEfficiency: number;
6 };
7};
8
9function withMetrics<T extends AnyFunction>(fn: T): MetricsWrapper<T> {
10 const metrics = {
11 calls: 0,
12 totalTime: 0,
13 cacheHits: 0,
14 };
15
16 const wrapped = (...args: Parameters<T>): ReturnType<T> => {
17 const start = performance.now();
18 const result = fn(...args);
19 metrics.totalTime += performance.now() - start;
20 metrics.calls += 1;
21
22 return result;
23 };
24
25 const enhanced = wrapped as MetricsWrapper<T>;
26 enhanced.getMetrics = () => ({
27 totalCalls: metrics.calls,
28 averageExecutionTime: metrics.totalTime / metrics.calls,
29 cacheEfficiency: metrics.cacheHits / metrics.calls,
30 });
31
32 return enhanced;
33}
Conclusión
La memoización en TypeScript se vuelve aún más poderosa cuando está correctamente tipada. Estas implementaciones ofrecen:
- Seguridad de tipos completa
- Errores detectados en tiempo de compilación
- Mejor mantenibilidad del código
- Documentación integrada a través de los tipos
Puntos Clave a Recordar
- Utiliza tipos estrictos en lugar de
any
- Define interfaces claras para tus opciones de configuración
- Aprovecha los genéricos para una mejor reutilización
- Implementa mecanismos de monitoreo type-safe
¡La combinación de la memoización y el sistema de tipos de TypeScript te permite optimizar tus aplicaciones mientras mantienes un código seguro y mantenible! 💪