dramaling-vocab-learning/frontend/lib/performance/index.ts

209 lines
4.6 KiB
TypeScript

// 前端性能優化工具模組
/**
* 防抖函數 - 防止過度頻繁的 API 調用
*/
export function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
}
/**
* 節流函數 - 限制函數執行頻率
*/
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return (...args: Parameters<T>) => {
if (!inThrottle) {
func.apply(null, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
/**
* 記憶化函數 - 快取函數執行結果
*/
export function memoize<T extends (...args: any[]) => any>(func: T): T {
const cache = new Map();
return ((...args: any[]) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(null, args);
cache.set(key, result);
return result;
}) as T;
}
/**
* 簡單的本地快取實作
*/
export class LocalCache {
private static instance: LocalCache;
private cache = new Map<string, { data: any; expiry: number }>();
public static getInstance(): LocalCache {
if (!LocalCache.instance) {
LocalCache.instance = new LocalCache();
}
return LocalCache.instance;
}
set(key: string, value: any, ttlMs: number = 300000): void { // 預設5分鐘
const expiry = Date.now() + ttlMs;
this.cache.set(key, { data: value, expiry });
}
get<T>(key: string): T | null {
const item = this.cache.get(key);
if (!item) {
return null;
}
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.data as T;
}
has(key: string): boolean {
const item = this.cache.get(key);
if (!item) return false;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return false;
}
return true;
}
clear(): void {
this.cache.clear();
}
// 清理過期項目
cleanup(): void {
const now = Date.now();
for (const [key, item] of this.cache) {
if (now > item.expiry) {
this.cache.delete(key);
}
}
}
}
/**
* API 請求快取包裝器
*/
export async function cachedApiCall<T>(
key: string,
apiCall: () => Promise<T>,
ttlMs: number = 300000
): Promise<T> {
const cache = LocalCache.getInstance();
// 檢查快取
const cached = cache.get<T>(key);
if (cached) {
console.log(`Cache hit for key: ${key}`);
return cached;
}
// 執行 API 調用
console.log(`Cache miss for key: ${key}, making API call`);
const result = await apiCall();
// 存入快取
cache.set(key, result, ttlMs);
return result;
}
/**
* 生成快取鍵
*/
export function generateCacheKey(prefix: string, ...params: any[]): string {
const paramString = params.map(p =>
typeof p === 'object' ? JSON.stringify(p) : String(p)
).join('_');
return `${prefix}_${paramString}`;
}
/**
* 性能監控工具
*/
export class PerformanceMonitor {
private static timers = new Map<string, number>();
static start(label: string): void {
this.timers.set(label, performance.now());
}
static end(label: string): number {
const startTime = this.timers.get(label);
if (!startTime) {
console.warn(`No timer found for label: ${label}`);
return 0;
}
const duration = performance.now() - startTime;
this.timers.delete(label);
console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms`);
return duration;
}
static measure<T>(label: string, fn: () => T): T {
this.start(label);
const result = fn();
this.end(label);
return result;
}
static async measureAsync<T>(label: string, fn: () => Promise<T>): Promise<T> {
this.start(label);
const result = await fn();
this.end(label);
return result;
}
}
/**
* 圖片懶加載 Hook 替代方案
*/
export function createIntersectionObserver(
callback: (entry: IntersectionObserverEntry) => void,
options?: IntersectionObserverInit
): IntersectionObserver {
const defaultOptions: IntersectionObserverInit = {
root: null,
rootMargin: '50px',
threshold: 0.1,
...options
};
return new IntersectionObserver((entries) => {
entries.forEach(callback);
}, defaultOptions);
}