L'ottimizzazione delle prestazioni è cruciale nello sviluppo di applicazioni moderne. Tra le tecniche di ottimizzazione, la memoizzazione si distingue come una soluzione elegante ed efficace per migliorare significativamente le prestazioni delle tue applicazioni TypeScript. In questa guida completa, scopri come utilizzare questa potente tecnica per ridurre il tempo di esecuzione delle tue funzioni fino al 90%. 🚀
Comprendere la Memoizzazione in TypeScript
La memoizzazione è una tecnica di ottimizzazione che consiste nel memorizzare in cache i risultati delle chiamate di funzione. Invece di ricalcolare lo stesso risultato più volte, lo memorizziamo per un utilizzo successivo. Questo approccio è particolarmente efficace per:
- Funzioni con calcoli intensivi
- Operazioni ricorsive
- Chiamate API
- Trasformazioni di dati complesse
Implementazione Type-Safe della Memoizzazione
Iniziamo definendo tipi precisi e un'implementazione sicura:
1// Tipi per la funzione di memoizzazione
2type AnyFunction = (...args: unknown[]) => unknown;
3type MemoizedFunction<T extends AnyFunction> = T & {
4 clearCache: () => void;
5};
6
7// Tipo per le voci della cache
8interface CacheEntry<T> {
9 value: T;
10 timestamp: number;
11}
12
13// Funzione di memoizzazione base con tipizzazione stretta
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 hit per la chiave: ${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(`Nuovo calcolo per la chiave: ${key}`);
32 return result;
33 };
34
35 // Aggiunta di un metodo per svuotare la cache
36 const memoizedWithClear = memoized as MemoizedFunction<T>;
37 memoizedWithClear.clearCache = () => cache.clear();
38
39 return memoizedWithClear;
40}
Implementazione Avanzata con Configurazione Tipizzata
1// Tipi per le opzioni di configurazione
2interface MemoizeOptions {
3 maxCacheSize: number;
4 ttl: number; // Time To Live in millisecondi
5 cacheKeyGenerator?: <T extends unknown[]>(...args: T) => string;
6}
7
8// Tipo per le statistiche della cache
9interface CacheStats {
10 hits: number;
11 misses: number;
12 size: number;
13 averageAccessTime: number;
14}
15
16// Implementazione avanzata con gestione completa della cache
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 // Verifica della validità della cache
50 if (cached && now - cached.timestamp <= ttl) {
51 updateStats(true);
52 return cached.value;
53 }
54
55 // Rimozione delle voci scadute se necessario
56 if (cached) {
57 cache.delete(key);
58 }
59
60 // Gestione della dimensione della cache
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 // Aggiunta dei metodi di utilità
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}
Esempi Pratici Type-Safe
1. Calcolo della Sequenza di Fibonacci con Tipi Stretti
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. Richieste API Tipizzate
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(
14 `Impossibile recuperare l'utente: ${response.statusText}`,
15 );
16 }
17 return response.json();
18 },
19 {
20 ttl: 300000, // Cache di 5 minuti
21 maxCacheSize: 100,
22 cacheKeyGenerator: (args) => args[0], // Usa direttamente l'ID come chiave
23 },
24);
3. Trasformazione dei Dati con Validazione
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 // Logica di trasformazione...
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);
Monitoraggio Type-Safe delle Prestazioni
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}
Conclusione
La memoizzazione in TypeScript diventa ancora più potente quando è correttamente tipizzata. Queste implementazioni offrono:
- Sicurezza completa dei tipi
- Errori rilevati in fase di compilazione
- Migliore manutenibilità del codice
- Documentazione integrata attraverso i tipi
Punti Chiave da Ricordare
- Usa tipi stretti invece di
any
- Definisci interfacce chiare per le tue opzioni di configurazione
- Sfrutta i generics per una migliore riutilizzabilità
- Implementa meccanismi di monitoraggio type-safe
La combinazione della memoizzazione e del sistema di tipi di TypeScript ti permette di ottimizzare le tue applicazioni mantenendo un codice sicuro e manutenibile! 💪