UNPKG

mobile-react-infinite-calendar

Version:

A mobile-optimized infinite scroll calendar component for React

125 lines (124 loc) 5.63 kB
/** * 자동 높이 계산 커스텀 훅 */ import { useEffect, useRef, useCallback, useMemo } from 'react'; import { AUTO_HEIGHT_DEFAULTS } from '../constants/calendar'; import { uiLogger } from '../utils/logger'; export function useAutoHeight({ containerRef, height, autoHeight, onHeightChange }) { const lastCalculatedHeight = useRef(null); const calculationTimeoutRef = useRef(null); const isCalculatingRef = useRef(false); const hasInitializedRef = useRef(false); const hasCalculatedRef = useRef(false); // autoHeight 옵션 메모이제이션 const memoizedOptions = useMemo(() => { if (!autoHeight) return null; return { topOffset: AUTO_HEIGHT_DEFAULTS.TOP_OFFSET, bottomOffset: AUTO_HEIGHT_DEFAULTS.BOTTOM_OFFSET, minHeight: AUTO_HEIGHT_DEFAULTS.MIN_HEIGHT, maxHeight: AUTO_HEIGHT_DEFAULTS.MAX_HEIGHT(), ...autoHeight }; }, [autoHeight]); // 높이 계산 함수 (최적화된 버전) const calculateHeight = useCallback(() => { // 중복 계산 방지 if (isCalculatingRef.current || !containerRef.current || !memoizedOptions) { uiLogger.debug('높이 계산 스킵', { isCalculating: isCalculatingRef.current, hasContainer: !!containerRef.current, hasOptions: !!memoizedOptions }); return; } isCalculatingRef.current = true; try { const rect = containerRef.current.getBoundingClientRect(); // 뷰포트 기준 top 위치를 사용하여 정확한 계산 const containerTop = rect.top; const topPosition = containerTop + memoizedOptions.topOffset; const availableSpace = window.innerHeight - topPosition - memoizedOptions.bottomOffset; // 최소/최대 높이 제한 const calculatedHeight = Math.min(Math.max(memoizedOptions.minHeight, availableSpace), memoizedOptions.maxHeight); // 의미있는 변화만 업데이트 (최소 5px 차이) const heightDiff = Math.abs((lastCalculatedHeight.current || 0) - calculatedHeight); if (heightDiff >= 5) { uiLogger.debug('자동 높이 계산', { previousHeight: lastCalculatedHeight.current, calculatedHeight, heightDiff, windowHeight: window.innerHeight, containerTop, availableSpace }); lastCalculatedHeight.current = calculatedHeight; onHeightChange(calculatedHeight); } } finally { isCalculatingRef.current = false; } }, [memoizedOptions]); // 강화된 디바운싱 (300ms) const debouncedCalculateHeight = useCallback(() => { if (calculationTimeoutRef.current) { clearTimeout(calculationTimeoutRef.current); } calculationTimeoutRef.current = setTimeout(calculateHeight, 300); }, []); useEffect(() => { // height가 'auto'이거나 autoHeight 옵션이 있는 경우 자동 계산 const shouldAutoCalculate = height === 'auto' || Boolean(autoHeight); // 초기화 시에만 설정 if (!hasInitializedRef.current) { uiLogger.info('자동 높이 계산 초기화', { height, autoHeight, shouldAutoCalculate, isNumberHeight: typeof height === 'number' }); hasInitializedRef.current = true; } if (shouldAutoCalculate && typeof height !== 'number' && memoizedOptions) { // 초기 계산 및 재시도 if (!hasCalculatedRef.current) { // 즉시 계산 시도 calculateHeight(); // DOM이 완전히 준비되지 않았을 경우를 위한 재시도 const timeoutId = setTimeout(() => { calculateHeight(); // 계산 성공 여부 확인 if (lastCalculatedHeight.current && lastCalculatedHeight.current > memoizedOptions.minHeight) { hasCalculatedRef.current = true; } else { // 계산 실패 시 추가 재시도 setTimeout(() => { calculateHeight(); hasCalculatedRef.current = true; }, 300); } }, 50); // resize 이벤트 등록 window.addEventListener('resize', debouncedCalculateHeight, { passive: true }); return () => { clearTimeout(timeoutId); if (calculationTimeoutRef.current) { clearTimeout(calculationTimeoutRef.current); } window.removeEventListener('resize', debouncedCalculateHeight); isCalculatingRef.current = false; }; } else { // 이미 계산된 경우 resize 이벤트만 등록 window.addEventListener('resize', debouncedCalculateHeight, { passive: true }); return () => { window.removeEventListener('resize', debouncedCalculateHeight); }; } } }, [height, memoizedOptions]); // 함수들을 의존성에서 제거하여 무한 루프 방지 }