L'optimisation des performances est cruciale dans le développement d'applications modernes. Parmi les techniques d'optimisation, la mémoïsation se distingue comme une solution élégante et efficace pour améliorer significativement les performances de vos applications TypeScript. Dans ce guide complet, découvrez comment utiliser cette technique puissante pour réduire le temps d'exécution de vos fonctions jusqu'à 90%. 🚀
Comprendre la Mémoïsation en TypeScript
La mémoïsation est une technique d'optimisation qui consiste à mettre en cache les résultats des appels de fonction. Au lieu de recalculer le même résultat plusieurs fois, nous le stockons en mémoire pour une réutilisation ultérieure. Cette approche est particulièrement efficace pour :
- Les fonctions avec des calculs intensifs
- Les opérations récursives
- Les appels d'API
- Les transformations de données complexes
Implémentation Type-Safe de la Mémoïsation
Commençons par définir des types précis et une implémentation sûre :
1// Types pour la fonction de mémoïsation
2type AnyFunction = (...args: unknown[]) => unknown;
3type MemoizedFunction<T extends AnyFunction> = T & {
4 clearCache: () => void;
5};
6
7// Type pour les entrées de cache
8interface CacheEntry<T> {
9 value: T;
10 timestamp: number;
11}
12
13// Fonction de mémoïsation basique avec typage strict
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 for key: ${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(`New calculation for key: ${key}`);
32 return result;
33 };
34
35 // Ajout d'une méthode pour vider le cache
36 const memoizedWithClear = memoized as MemoizedFunction<T>;
37 memoizedWithClear.clearCache = () => cache.clear();
38
39 return memoizedWithClear;
40}
Implémentation Avancée avec Configuration Typée
1// Types pour les options de configuration
2interface MemoizeOptions {
3 maxCacheSize: number;
4 ttl: number; // Time To Live en millisecondes
5 cacheKeyGenerator?: <T extends unknown[]>(...args: T) => string;
6}
7
8// Type pour les statistiques de cache
9interface CacheStats {
10 hits: number;
11 misses: number;
12 size: number;
13 averageAccessTime: number;
14}
15
16// Implémentation avancée avec gestion complète du 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 // Vérification de la validité du cache
50 if (cached && now - cached.timestamp <= ttl) {
51 updateStats(true);
52 return cached.value;
53 }
54
55 // Suppression des entrées expirées si nécessaire
56 if (cached) {
57 cache.delete(key);
58 }
59
60 // Gestion de la taille du 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 // Ajout des méthodes utilitaires
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}
Exemples Pratiques Type-Safe
1. Calcul de Suite de Fibonacci avec Types Stricts
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. Requêtes API Typées
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(`Failed to fetch user: ${response.statusText}`);
14 }
15 return response.json();
16 },
17 {
18 ttl: 300000, // Cache de 5 minutes
19 maxCacheSize: 100,
20 cacheKeyGenerator: (args) => args[0], // Utilise directement l'ID comme clé
21 },
22);
3. Transformation de Données avec Validation
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 // Logique de transformation...
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);
Surveillance Type-Safe des Performances
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}
Conclusion
La mémoïsation en TypeScript devient encore plus puissante lorsqu'elle est correctement typée. Ces implémentations offrent :
- Une sécurité de type complète
- Des erreurs détectées à la compilation
- Une meilleure maintenabilité du code
- Une documentation intégrée via les types
Points Clés à Retenir
- Utilisez des types stricts plutôt que
any
- Définissez des interfaces claires pour vos options de configuration
- Tirez parti des génériques pour une meilleure réutilisabilité
- Implémentez des mécanismes de surveillance type-safe
La combinaison de la mémoïsation et du système de types de TypeScript vous permet d'optimiser vos applications tout en maintenant un code sûr et maintenable ! 💪