swiper 캘린더 최적화 하기


인턴십 중 날짜별 예약 기능을 구현해야 하는 상황이 생겼다.
디자이너의 퍼블리싱 화면대로 구현을 해야 했다.
주마다 슬라이드로 캘린더를 구현하고, 예약 가능 목록을 구현해야 했다.
사내에서 슬라이드 라이브러리를 swiper
를 사용하고 있었다.
처음 로드시 몇 주치 데이터를 가져와야 하는 기준의 모호함과
한 번에 많은 주차를 불러온다면 효율성이 떨어진다고 생각했다.
그래서 3주치씩 데이터를 동적으로 로드하는 방식을 생각했다.
슬라이드가 완료되면 현재 슬라이드의 앞뒤 주만을 가지고 데이터를 다루는 것이다.

위 그림과 같이 3주치 데이터를 가지고 동적으로 로드하기로 했다.
코드 작성
우선 useDateSelector
훅을 작성했다.
typescript// use-date-selector.tsimport { addWeeks, eachDayOfInterval, endOfWeek, startOfWeek } from 'date-fns';import { useState, useCallback } from 'react'; // input date에 해당하는 1주일치 날짜 목록 생성 함수const generateWeek = (date: Date) => { const startOfThisWeek = startOfWeek(date, { weekStartsOn: 0 }); const endOfThisWeek = endOfWeek(date, { weekStartsOn: 0 }); return eachDayOfInterval({ start: startOfThisWeek, end: endOfThisWeek });}; // 초기 3주치 날짜 데이터const initWeeks = (initDate: Date, buffer: number = 2) => { const weeks = []; let currentWeekStart = startOfWeek(initDate, { weekStartsOn: 0 }); for (let i = 0; i < buffer; i++) { weeks.push(generateWeek(currentWeekStart)); currentWeekStart = addWeeks(currentWeekStart, 1); } return weeks;}; export const useDateSelector = (initDate: Date) => { const [selectedDate, setSelectedDate] = useState<Date>(new Date()); const [weeks, setWeeks] = useState<Date[][]>(initWeeks(initDate, 3)); const [currentSlide, setCurrentSlide] = useState<number>(0); const fetchWeeks = (date: Date) => { setWeeks((prevWeeks) => [...prevWeeks, generateWeek(date)]); }; const handleNextSlide = useCallback(() => { setCurrentSlide((prev) => prev + 1); const lastWeek = weeks[weeks.length - 1]; fetchWeeks(addWeeks(lastWeek[0], 1)); }, [weeks, fetchWeeks]); const handlePrevSlide = useCallback(() => { setCurrentSlide((prev) => Math.max(0, prev - 1)); }, []); return { selectedDate, setSelectedDate, weeks, fetchWeeks, handleNextSlide, handlePrevSlide, currentSlide, };};
그리고 DateSelector
컴포넌트를 만들었다.
tsx// /date-selector.tsximport { Swiper, SwiperSlide } from 'swiper/react';import { format, isPast } from 'date-fns';import { ko } from 'date-fns/locale'; // ... export const DateSelector = ({ weeks, selectedDate, setSelectedDate, currentSlide, handleNextSlide, handlePrevSlide,}: Props) => { return ( <Swiper pagination={{ type: 'fraction', clickable: true, }} scrollbar={{ draggable: true }} onSlideNextTransitionEnd={handleNextSlide} onSlidePrevTransitionEnd={handlePrevSlide} initialSlide={currentSlide} > {weeks.map((week, i) => ( <SwiperSlide key={i}> <div> {week.map((d: Date) => ( <div key={d.toString()}> <div className="day">{format(d, 'E', { locale: ko })}</div> <div onClick={() => setSelectedDate(d)}>{format(d, 'dd')}</div> </div> ))} </div> </SwiperSlide> ))} </Swiper> );};
결과

커스텀 훅을 제대로 분리해서 사용한 첫 경험이었다.
예약 화면을 구성하면서 팀장님께 코드를 깔끔하게 짜고 모듈화를 잘 한다는 칭찬을 받았다.
퇴근길에 아이디어가 생각나 바로 카페로 달려가 작업했는데
결과가 좋아 상당히 뿌듯하다.