Loto / src /utils /PerformanceOptimizer.ts
Raí Santos
oi
4c1e4ec
/**
* Utilitários para otimização de performance
*/
// Cache otimizado com LRU (Least Recently Used)
export class LRUCache<K, V> {
private cache = new Map<K, V>();
private readonly maxSize: number;
constructor(maxSize: number = 100) {
this.maxSize = maxSize;
}
get(key: K): V | undefined {
const value = this.cache.get(key);
if (value !== undefined) {
// Move para o final (mais recente)
this.cache.delete(key);
this.cache.set(key, value);
}
return value;
}
set(key: K, value: V): void {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.maxSize) {
// Remove o mais antigo (primeiro da Map)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
clear(): void {
this.cache.clear();
}
size(): number {
return this.cache.size;
}
}
// Debounce otimizado
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number,
immediate?: boolean
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return function executedFunction(...args: Parameters<T>) {
const later = () => {
timeout = null;
if (!immediate) func(...args);
};
const callNow = immediate && !timeout;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func(...args);
};
}
// Throttle otimizado
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean = false;
return function executedFunction(...args: Parameters<T>) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Memoização otimizada
export function memoize<T extends (...args: any[]) => any>(
fn: T,
getKey?: (...args: Parameters<T>) => string
): T & { cache: LRUCache<string, ReturnType<T>>; clearCache: () => void } {
const cache = new LRUCache<string, ReturnType<T>>(50);
const memoized = function (...args: Parameters<T>): ReturnType<T> {
const key = getKey ? getKey(...args) : JSON.stringify(args);
const cached = cache.get(key);
if (cached !== undefined) {
return cached;
}
const result = fn(...args);
cache.set(key, result);
return result;
} as T & { cache: LRUCache<string, ReturnType<T>>; clearCache: () => void };
memoized.cache = cache;
memoized.clearCache = () => cache.clear();
return memoized;
}
// Worker Pool para cálculos pesados
export class WorkerPool {
private workers: Worker[] = [];
private queue: Array<{
resolve: (value: any) => void;
reject: (error: any) => void;
data: any;
}> = [];
private busyWorkers = new Set<Worker>();
constructor(workerScript: string, poolSize: number = navigator.hardwareConcurrency || 4) {
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerScript);
worker.onmessage = (e) => this.handleWorkerMessage(worker, e);
worker.onerror = (e) => this.handleWorkerError(worker, e);
this.workers.push(worker);
}
}
execute<T>(data: any): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push({ resolve, reject, data });
this.processQueue();
});
}
private processQueue(): void {
if (this.queue.length === 0) return;
const availableWorker = this.workers.find(w => !this.busyWorkers.has(w));
if (!availableWorker) return;
const task = this.queue.shift()!;
this.busyWorkers.add(availableWorker);
(availableWorker as any)._currentTask = task;
availableWorker.postMessage(task.data);
}
private handleWorkerMessage(worker: Worker, e: MessageEvent): void {
const task = (worker as any)._currentTask;
if (task) {
task.resolve(e.data);
this.busyWorkers.delete(worker);
delete (worker as any)._currentTask;
this.processQueue();
}
}
private handleWorkerError(worker: Worker, e: ErrorEvent): void {
const task = (worker as any)._currentTask;
if (task) {
task.reject(e);
this.busyWorkers.delete(worker);
delete (worker as any)._currentTask;
this.processQueue();
}
}
terminate(): void {
this.workers.forEach(worker => worker.terminate());
this.workers = [];
this.queue = [];
this.busyWorkers.clear();
}
}
// Lazy loading de imagens otimizado
export class LazyImageLoader {
private observer: IntersectionObserver;
private images = new Set<HTMLImageElement>();
constructor(rootMargin: string = '50px') {
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement;
this.loadImage(img);
}
});
},
{ rootMargin }
);
}
observe(img: HTMLImageElement): void {
this.images.add(img);
this.observer.observe(img);
}
unobserve(img: HTMLImageElement): void {
this.images.delete(img);
this.observer.unobserve(img);
}
private loadImage(img: HTMLImageElement): void {
const src = img.dataset.src;
if (src) {
img.src = src;
img.onload = () => {
img.classList.add('loaded');
this.observer.unobserve(img);
};
}
}
disconnect(): void {
this.observer.disconnect();
this.images.clear();
}
}
// Monitor de performance
export class PerformanceMonitor {
private metrics = new Map<string, number[]>();
private observers: PerformanceObserver[] = [];
constructor() {
this.setupObservers();
}
private setupObservers(): void {
if ('PerformanceObserver' in window) {
// Long tasks
try {
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.addMetric('long-task', entry.duration);
}
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
this.observers.push(longTaskObserver);
} catch (e) {
// Long tasks não suportado
}
// Layout shifts
try {
const layoutShiftObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if ((entry as any).value && !(entry as any).hadRecentInput) {
this.addMetric('layout-shift', (entry as any).value);
}
}
});
layoutShiftObserver.observe({ entryTypes: ['layout-shift'] });
this.observers.push(layoutShiftObserver);
} catch (e) {
// Layout shift não suportado
}
}
}
addMetric(name: string, value: number): void {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
this.metrics.get(name)!.push(value);
}
getMetrics(): Record<string, { avg: number; max: number; count: number }> {
const result: Record<string, { avg: number; max: number; count: number }> = {};
this.metrics.forEach((values, name) => {
result[name] = {
avg: values.reduce((a, b) => a + b, 0) / values.length,
max: Math.max(...values),
count: values.length
};
});
return result;
}
clear(): void {
this.metrics.clear();
}
disconnect(): void {
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
}
}
// Instâncias globais
export const performanceMonitor = new PerformanceMonitor();
export const imageLoader = new LazyImageLoader();
// Hook para uso em React
export function usePerformanceMetrics() {
return {
getMetrics: () => performanceMonitor.getMetrics(),
addMetric: (name: string, value: number) => performanceMonitor.addMetric(name, value),
clear: () => performanceMonitor.clear()
};
}