@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
303 lines (250 loc) • 7.49 kB
text/typescript
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
// Debounce hook
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Throttle hook
export function useThrottle<T>(value: T, limit: number): T {
const [throttledValue, setThrottledValue] = useState<T>(value);
const lastRan = useRef<number>(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastRan.current >= limit) {
setThrottledValue(value);
lastRan.current = Date.now();
}
}, limit - (Date.now() - lastRan.current));
return () => {
clearTimeout(handler);
};
}, [value, limit]);
return throttledValue;
}
// Virtual scrolling hook for large lists
export function useVirtualScroll<T>(
items: T[],
itemHeight: number,
containerHeight: number,
overscan: number = 5
) {
const [scrollTop, setScrollTop] = useState(0);
const visibleItems = useMemo(() => {
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + overscan,
items.length - 1
);
const visibleStartIndex = Math.max(0, startIndex - overscan);
return {
startIndex: visibleStartIndex,
endIndex,
items: items.slice(visibleStartIndex, endIndex + 1),
offsetY: visibleStartIndex * itemHeight,
totalHeight: items.length * itemHeight,
};
}, [items, itemHeight, containerHeight, scrollTop, overscan]);
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
setScrollTop(event.currentTarget.scrollTop);
}, []);
return {
...visibleItems,
handleScroll,
};
}
// Pagination hook
export function usePagination<T>(items: T[], itemsPerPage: number = 10) {
const [currentPage, setCurrentPage] = useState(1);
const paginationData = useMemo(() => {
const totalPages = Math.ceil(items.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentItems = items.slice(startIndex, endIndex);
return {
currentItems,
currentPage,
totalPages,
totalItems: items.length,
itemsPerPage,
hasNextPage: currentPage < totalPages,
hasPrevPage: currentPage > 1,
};
}, [items, currentPage, itemsPerPage]);
const goToPage = useCallback(
(page: number) => {
setCurrentPage(Math.max(1, Math.min(page, paginationData.totalPages)));
},
[paginationData.totalPages]
);
const nextPage = useCallback(() => {
if (paginationData.hasNextPage) {
setCurrentPage((prev) => prev + 1);
}
}, [paginationData.hasNextPage]);
const prevPage = useCallback(() => {
if (paginationData.hasPrevPage) {
setCurrentPage((prev) => prev - 1);
}
}, [paginationData.hasPrevPage]);
const reset = useCallback(() => {
setCurrentPage(1);
}, []);
return {
...paginationData,
goToPage,
nextPage,
prevPage,
reset,
};
}
// Cache hook with LRU eviction
export function useCache<K, V>(maxSize: number = 100) {
const cache = useRef<Map<K, { value: V; timestamp: number }>>(new Map());
const get = useCallback((key: K): V | undefined => {
const item = cache.current.get(key);
if (item) {
// Update timestamp for LRU
item.timestamp = Date.now();
return item.value;
}
return undefined;
}, []);
const set = useCallback(
(key: K, value: V): void => {
// If cache is full, remove oldest item
if (cache.current.size >= maxSize) {
let oldestKey: K | undefined;
let oldestTimestamp = Date.now();
for (const [k, v] of cache.current.entries()) {
if (v.timestamp < oldestTimestamp) {
oldestTimestamp = v.timestamp;
oldestKey = k;
}
}
if (oldestKey !== undefined) {
cache.current.delete(oldestKey);
}
}
cache.current.set(key, { value, timestamp: Date.now() });
},
[maxSize]
);
const has = useCallback((key: K): boolean => {
return cache.current.has(key);
}, []);
const clear = useCallback((): void => {
cache.current.clear();
}, []);
const size = useCallback((): number => {
return cache.current.size;
}, []);
return { get, set, has, clear, size };
}
// Background task processing hook
export function useBackgroundTasks() {
const tasks = useRef<
Array<{ id: string; task: () => Promise<any>; priority: number }>
>([]);
const [isProcessing, setIsProcessing] = useState(false);
const processingRef = useRef(false);
const addTask = useCallback(
(taskFn: () => Promise<any>, priority: number = 1, id?: string) => {
const taskId = id || `task_${Date.now()}_${Math.random()}`;
tasks.current.push({ id: taskId, task: taskFn, priority });
// Sort by priority (higher priority first)
tasks.current.sort((a, b) => b.priority - a.priority);
processNextTask();
return taskId;
},
[]
);
const processNextTask = useCallback(async () => {
if (processingRef.current || tasks.current.length === 0) {
return;
}
processingRef.current = true;
setIsProcessing(true);
try {
const nextTask = tasks.current.shift();
if (nextTask) {
await nextTask.task();
}
} catch (error) {
console.error("Background task failed:", error);
} finally {
processingRef.current = false;
setIsProcessing(tasks.current.length > 0);
// Process next task if any
if (tasks.current.length > 0) {
setTimeout(processNextTask, 0);
}
}
}, []);
const clearTasks = useCallback(() => {
tasks.current = [];
setIsProcessing(false);
}, []);
const getQueueLength = useCallback(() => {
return tasks.current.length;
}, []);
return {
addTask,
clearTasks,
getQueueLength,
isProcessing,
queueLength: tasks.current.length,
};
}
// Performance monitoring hook
export function usePerformanceMonitor() {
const [metrics, setMetrics] = useState<{
renderTime: number;
memoryUsage?: number;
componentMounts: number;
}>({
renderTime: 0,
componentMounts: 0,
});
const startTime = useRef<number>(0);
const mountCount = useRef<number>(0);
useEffect(() => {
mountCount.current += 1;
startTime.current = performance.now();
return () => {
const endTime = performance.now();
const renderTime = endTime - startTime.current;
setMetrics((prev) => ({
...prev,
renderTime,
componentMounts: mountCount.current,
memoryUsage: (performance as any).memory?.usedJSHeapSize,
}));
};
}, []);
const measureFunction = useCallback(
<T extends any[], R>(fn: (...args: T) => R, name?: string) => {
return (...args: T): R => {
const start = performance.now();
const result = fn(...args);
const end = performance.now();
if (name) {
console.log(`${name} took ${end - start} milliseconds`);
}
return result;
};
},
[]
);
return {
metrics,
measureFunction,
};
}