feat: 모바일 일정 검색에 가상 스크롤 적용
- @tanstack/react-virtual useVirtualizer 적용 - 동적 높이 지원 (measureElement, data-index) - SEARCH_LIMIT 10 → 20으로 증가 - 검색 결과가 많아도 DOM에는 화면에 보이는 요소만 렌더링
This commit is contained in:
parent
b35dab5eea
commit
660acd0007
1 changed files with 68 additions and 31 deletions
|
|
@ -3,6 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion';
|
|||
import { Clock, Tag, Link2, ChevronLeft, ChevronRight, ChevronDown, Search, X, Calendar } from 'lucide-react';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { getSchedules, getCategories, searchSchedules } from '../../../api/public/schedules';
|
||||
|
||||
// 모바일 일정 페이지
|
||||
|
|
@ -26,7 +27,9 @@ function MobileSchedule() {
|
|||
setCalendarViewDate(newDate);
|
||||
};
|
||||
|
||||
const SEARCH_LIMIT = 10;
|
||||
const SEARCH_LIMIT = 20; // 페이지당 20개
|
||||
const ESTIMATED_ITEM_HEIGHT = 100; // 아이템 추정 높이 (동적 측정)
|
||||
const scrollContainerRef = useRef(null); // 가상 스크롤 컨테이너
|
||||
const { ref: loadMoreRef, inView } = useInView({ threshold: 0, rootMargin: '100px' });
|
||||
|
||||
// 검색 무한 스크롤
|
||||
|
|
@ -55,6 +58,14 @@ function MobileSchedule() {
|
|||
return searchData.pages.flatMap(page => page.schedules);
|
||||
}, [searchData]);
|
||||
|
||||
// 가상 스크롤 설정 (검색 모드에서만 활성화, 동적 높이 지원)
|
||||
const virtualizer = useVirtualizer({
|
||||
count: isSearchMode && searchTerm ? searchResults.length : 0,
|
||||
getScrollElement: () => scrollContainerRef.current,
|
||||
estimateSize: () => ESTIMATED_ITEM_HEIGHT,
|
||||
overscan: 5, // 버퍼 아이템 수
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (inView && hasNextPage && !isFetchingNextPage && isSearchMode && searchTerm) {
|
||||
fetchNextPage();
|
||||
|
|
@ -436,12 +447,11 @@ function MobileSchedule() {
|
|||
</AnimatePresence>
|
||||
|
||||
{/* 컨텐츠 영역 */}
|
||||
<div className="mobile-content" ref={contentRef}>
|
||||
<div className="mobile-content" ref={isSearchMode && searchTerm ? scrollContainerRef : contentRef}>
|
||||
<div className="px-4 pt-4 pb-4">
|
||||
{isSearchMode && searchTerm ? (
|
||||
// 검색 결과
|
||||
<div className="space-y-3">
|
||||
{searchLoading ? (
|
||||
// 검색 결과 - 가상 스크롤
|
||||
searchLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
|
|
@ -451,14 +461,42 @@ function MobileSchedule() {
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
{searchResults.map((schedule, index) => (
|
||||
<div
|
||||
style={{
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{virtualizer.getVirtualItems().map((virtualItem) => {
|
||||
const schedule = searchResults[virtualItem.index];
|
||||
if (!schedule) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={virtualItem.key}
|
||||
ref={virtualizer.measureElement}
|
||||
data-index={virtualItem.index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
>
|
||||
<div className={virtualItem.index < searchResults.length - 1 ? "pb-3" : ""}>
|
||||
<ScheduleCard
|
||||
key={`${schedule.id}-search-${index}`}
|
||||
schedule={schedule}
|
||||
categoryColor={getCategoryColor(schedule.category_id)}
|
||||
categories={categories}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* 무한 스크롤 트리거 */}
|
||||
<div ref={loadMoreRef} className="py-4">
|
||||
{isFetchingNextPage && (
|
||||
<div className="flex justify-center">
|
||||
|
|
@ -467,8 +505,7 @@ function MobileSchedule() {
|
|||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : loading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue