UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

303 lines (250 loc) 7.49 kB
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, }; }