대략 1년전 22년 2월에 입사 했을때 두번째로 진행한 feature 가
챌린지 인증이 가능한 기간을 사용자들에게 보여주는 캘린더 UI 개발 이였다.
이때까지만 해도 디자이너분들에게 절대 복종 🫡 이였기 때문에 (지금은 가끔 반항할때도 있음)
기능적 요구사항을 파악하거나 등의 과정을 거의 스킵한 채
빠르게 피그마만 보면서 UI만 똑같이 그려나가기 시작했다.
그래서 대략 1주일 공을 쏟아서 완성한 결과물 🥳
이때까지만 해도 잘 해냈다고 생각했고 만족스러운 결과물이 나왔다고 생각했다.
그러다 시간이 대략 1년 정도 지나서 작년 12월 랜선대회 feature 를 진행하면서
캘린더 개발에 대한 요구사항이 또 들어왔다.
Product Designer: 제리 ~ 지난번에 캘린더 개발 했으니까 빠르게 끝낼 수 있죠?
Jerry: 어.. 아니요.. 그거 못 쓸거 같은데.. 거의 처음부터 다시 개발해야할 것 같아요.
이때 기존에 챌린지 캘린더를 개발할 때 문제점이 뭐였는지를 고민하기 시작했다.
대략 이런 문제점으로 코드 재활용성이 꽝이 되어 버렸기에 처음부터 모든걸 다시 개발 해야하는 상황이 발생했다.
Headless 에 대한 글을 종종 봤었는데 실제로 적용 하려니까 막상 어떤식으로 개발해야할지 감이 안왔었는데 좋은 예시는 가까운 곳에 있었다.
리액트 네이티브에서 FlatList 가 정말 최고의 컴포넌트라고 느끼고 대부분의 리스트를 FlatList 로 개발하고 있었는데
왜 이렇게 사용성이 좋을까? 를 고민 했을때
renderItem
이 최고의 장점이라고 생각이 들었다.
FlatList API 는 데이터를 어떻게 그릴지 renderItem
을 통해 스타일을 직접 정의할 수 있었고 data
와 구분 지어서
도메인 로직 (data) 와 UI (renderItem) 을 분리해서 개발할 수 있도록 유도했었고 여기서 힌트를 얻었다.
그렇게 개발하게 된 캘린더 V2 API 를 소개하자면 대략 아래와 같다.
type CalendarProps<T> = {
startDate: Date;
endDate: Date;
initialActiveDate?: Date;
itemHeight: number;
itemWidth?: number;
bgColor?: ColorKeys;
display: 'month' | 'week';
CalendarHeaderComponent?: React.ReactElement;
children?: React.ReactNode;
onViewableMonthChanged?: (date: Date) => void;
} & Partial<FlatListProps<CalendarDateItem[]>> &
Pick<CalendarRenderProps<T>, 'dayItemMapFn' | 'renderDayItem'>;
여기서 핵심 개념이 dayItemMapFn
와 CalendarDateItem
에 대한 타입인데
우선 CalendarDateItem
이 하나의 날짜에 대한 최소한의 정보를 나타내는 데이터
type CalendarDateItem = {
type: 'date' | 'placeholder'; // 해당 달에 포함된 날짜인지 ex) 2월 캘린더에 포함된 1월 31일은 'placeholder' 에 해당 되는 날짜
date: Date; // 날짜
weekIndex: number; // 주 단위로 봤을때 해당일의 index
monthIndex: number; // 월 단위로 봤을때 해당일의 index
isBetweenRange: boolean; // startDate ~ endDate 사이인지
isToday: boolean; // 해당 날짜가 오늘인지
isActiveDate?: boolean; // 현재 커서가 가있는 날짜인지
};
여기에 각각 도메인 로직, 데이터를 매핑해주는 함수를 열어주었고
function Calendar<T extends CalendarDateItem>({
children,
renderDayItem,
dayItemMapFn,
// ... 생략
코드 레벨 예시를 조금 더 들자면
// CalendarDateItem 을 확장하는 도메인 로직이 포함된 데이터를 매핑하는 함수
const dayItemMapFn = useCallback(
(item: CalendarDateItem): MyCustomItemType => {
return {
...item,
backgroundImage: utils.filterImageByDate(images, item.date),
};
},
[images],
);
// FlatList renderItem 에 해당되는 renderDayItem
const renderDayItem = useCallback((item: MyCustomItemType) => {
return <MyCustomDayItem {...item} />;
}, []);
요런식으로 개발 해보았다.
이렇게 랜선대회용 캘린더가 완성이 되었다.
이제는 지난번과 같은 질문이 왔을때,
Product Designer: 제리 ~ 지난번에 캘린더 개발 했으니까 빠르게 끝낼 수 있죠?
Jerry: 어우 그럼유~. 지난번에 들었던 공수의 1/2 이면 되죠~
잘못된 내용 있으면 댓글 남겨주세요 ~ 🙇🏻♂️