Dooboo-UI Calendar Carousel 제작과정

Jessie Lee
Cross-Platform Korea
13 min readAug 24, 2020

dooboolab 에서 만든 첫 React-Native Calendar Component

두부랩에서 일하면서 저에게 주어진 첫번째 과제는 캘린더 컴포넌트 만들기였습니다. React-Native을 처음 접해보면서 캘린더를 만들었는데요, 결과는 아래와 같습니다 :)

간단히 말하자면 , CalendarCarousel 은 infinite scroll 이 가능한 React-Native UI 캘린더 입니다. 이벤트가 있는 날짜 표시하기, 오늘의 날짜 표시, 특정 날짜 선택하기 등의 기능이 있습니다.

이 포스트에서는 제가 CalendarCarousel를 만든 과정을 정리해보고자 합니다. 코드는 아래에 제공되어있습니다.

path info

../dooboo-ui/packages/CalendarCarousel/index.tsx
../dooboo-ui/stories/packages/CalendarCarousel.stories.tsx

그리고 여기는 이글의 영어버전입니다. :)

CalendarCarousel 만든과정

먼저 CalendarCarousel 의 index.tsx 파일을 살펴보도록 하겠습니다.

index.tsx는 CalendarCarousel이라는 함수를 리턴합니다. CalendarCarousel는 결국에는 3개의 renderCalendar() 이라는 함수를 ScrollView 에 감싸서 return 합니다. 여기서 이 ScrollView는 유저들이 캘린더를 넘기면서 볼 수 있게 해줍니다.

간단히 정리 하자면, 아래의 그림과 같이 구성되어있습니다.

renderCalendar()

renderCalendar() 는 크게 세가지 일을 합니다.

  • print Month Name
  • print Year
  • print dates of the Calendar

이제부터 이 위의 각각의 세가지 일들이 어떻게 일어나는지 설명해보겠습니다.

How is the MonthName printed?

monthNameIntl 을 이용해서 출력됩니다. 아래의 코드와 같습니다. IntlDateTimeFormat() 이용하여 currentDatemonthName 을 출력해줍니다.

How are the Year and Dates Printed?

년도와 날짜는 javascript Date Object 을 이용해서 출력됩니다.

왜 Moment 대신에 new Date() 을 사용했나요?

javascript Date object은 default으로 자바스크립트에 존재 합니다. 그래서 moment 처럼 따로 library를 받지 않고도 사용 가능합니다. 간단히 말해서, 따로 라이버리를 받지 않고도 가볍게 사용할 수 있다는 이점이 있어서 moment 대신에 javascript Date object을 사용했습니다.

Year

아래의 코드는 monthName 아래에 출력되어있는 년도를 출력하는 코드입니다. getFullYear() 라는 함수를 이용해서 displayDate의 년도를 숫자로 반환합니다.

const year = displayDate.getFullYear();

Dates

날짜는 세가지로 나눠서 출력했습니다.

  • prevDates
  • currentDates
  • nextDates

prevDates

Javascript Date의 요일은 0~6 까지의 index 가 있습니다. prevDates를 출력하기 위해서는 currentDate의 요일의 index를 아래처럼 구하고, firstWeekday라는 변수에 저장합니다.

const firstWeekday = new Date(year, month, 1).getDay();

그후, 새로운 배열을 만들고, prevDates 를 하나씩 unshift 를 이용하여 prevDates에 밀어 넣어줍니다.

const prevDates = [];
for (let idx = 0; idx < firstWeekday; idx++) {
const date = new Date(year, month, 0);
date.setDate(date.getDate() - idx);
prevDates.unshift(date);
}

(currentMonth)Dates

현재 달의 날짜들을 가져오기 위해서는 그 달의 마지막 요일을 lastDate 이라는 변수 속에 넣어주어야 합니다.

const lastDate = new Date(year, month + 1, 0).getDate();

이후, 빈 배열에 lastDate을 하나씩 넣어줍니다.

const dates = [];for (let idx = 1; idx <= lastDate; idx++) {
dates.push(new Date(year, month, idx));
}

nextDates

마지막으로 아래에 표시 되는 다음달의 날짜 들을 가져오기 위해서 현재 달의 마지막 요일을 lastWeekDay에 저장해줍니다.

const lastWeekday = new Date(year, month, lastDate).getDay();

그 후 다음 달의 날짜들을 nextDates 이라는 새로운 배열에 담에서 push 해줍니다.

const nextDates = [];if (6 - lastWeekday >= 1) {
for (let idx = 1; idx <= 6 - lastWeekday; idx++) {
nextDates.push(new Date(year, month + 1, idx));
}
}

여기서 각각의 배열에 들어가는 것은 날짜나 숫자가 아닌, Date object 입니다. 이렇게 해서 만들어진 세개의 날짜 배열들은 calendarDates 안으로 들어가게 됩니다.

How are Weekdays Printed?

여기서 weekday들은 toLocaleString()을 이용해서 출력됩니다. toLocaleString() 도 하나의 javascript Date object 입니다.

toLocaleString() 에서 narrow 는 요일들을 한단어로 축약해서 출력해줍니다. 예를 들면 Monday는 ‘M’ 으로 출력되게 되는 것입니다. 그리고 여기서 우리가 default으로 설정을 하면, weekdays는 유저의 컴퓨터에 설정된 default 언어로 표시되게 됩니다.

How renderCalendar() prints the Calendar

이렇게 출력된 monthName, year, 그리고 calendarDates는 renderCalendar라는 함수에 아래서처럼 모아서 출력됩니다.

CalendarCarousel Features

아래의 이미지에서, renderCalendar() 가 불러지게 되면, changeMonth(), renderDates(), 그리고 renderDayEvents() 라는 함수들도 불러지게 됩니다.

간단히 말하자면,

  • changeMonth() — 다음달 화살표 버튼 이 눌러지면 불러집니다.
  • renderDates() — 특정날짜에 표시되는 효과들을 render 해줍니다.
  • renderEvent() — 그날에 저장된 이베트의 text를 render해줍니다.

Display Features in renderDates()

이 캘린더에 있는 표시되는 Display Feature 들은 크게 세가지로 나뉠 수 있습니다.

  • isSelected() — 유저가 특정날짜를 선택했을때 배경에 연한 파란색의 원을 표시해 줍니다.
isSelected(): when a user clicks on a date
  • isToday() — 현재의 날짜에 파란색원을 표시해 줍니다.
isToday(): displays currentDate with a blue circle
  • hasEvent() — 이벤트가 있는 날짜에 파란색원을 표시해 줍니다.
hasEvent(): marks a date with an event with a small blue circle

이제 저는 각각 이 세가지 부분을 설명하겠습니다. 먼저, renderDates() 이라는 것은 FlatList 내부에서 불러지고, 각각의 Date object을 calendarDates안에서 부른 다음

이 코드를 자세히 보면 이렇게 생겼습니다.

isSelected()

isSelected()

이 효과는 연한 파란 원을 표시해 주는

코드는 이렇게 생겼습니다.

여기서 itemDay() 라는 것은,

const itemDay = dateItem.getDate();

isToday()

isToday()

이 효과는 현재의 날짜를 파란 원으로 표시해 줍니다. 아래에 이 코드가 있습니다. 코드에서 dateItem이라는 것은 calendarDates안에 들어있는 Date object 하나하나를 일컫습니다.

hasEvent()

hasEvent()

hasEvent() 에 사용 되는 markedDates, Months, and Years은 아래처럼 정의 됩니다. selectedEventDate 의 date, month, and year를 가지고 사용되게 됩니다.

markedDayEvents는 아래처럼 생긴 Object data array 입니다. 이런 Object data array는 Props를 통해서 불려지게 됩니다. 현재는 아래의 값들을 기본으로 설정해 놓았습니다.

ScrollingFeatures

이 캘린더를 넘길 수 있는 방법으로는 두 가지가 있습니다.

  • 화살표를 이용한다. — changeMonth()
  • swipe feature를 이용한다. — ScrollView

changeMonth()

만약 전달로 넘어가는 화살표가 눌러진다면, toPrevMonth true로 설정되게 됩니다. 그리고 여기에 있는 currentDate은 prevMonth 으로 업데이트가 되게 됩니다.

ScrollView

ScrollView 는 renderCalendars()를 감싸줍니다. 이 함수는 3개의 renderCalendar() 를 불러줍니다. 우리는 여기서 3개의 renderCalendar() 함수를 부르고

위의 ScrollView를 살펴보면 ScrollViewProps 중에 하나로 onMomentumScrollEnd 이라는 함수가 불러온다는 것을 볼 수 있습니다.

contentOffset — 처음에 화면이 로딩되는 위치를 지정해줍니다. Default Value {x:0, y:0}

layoutWidth — ScrollView container에서 현재 캘린더의 가로축의 위치가 어디있는지를 의미합니다.

scrollEffect()

scrollEffect()라는 함수는 변수로 native event를 받게 됩니다. 여기서 ‘event’ 라는 것은 유저가 왼쪽, 혹은 오른쪽으로 넘기는 이벤트를 의미합니다. scrollEffect 라는 함수는 현재함수의 위치를 보고, 어떠한 값들을 가지고 있는지 확인한 후, calendar를 어떻게 scroll하는지에 따라 업데이트 해줍니다.

만약 xValue (contentOffset of x )가 0 이라면 유저가 전달로 스크롤 했다는 의미입니다. 반면, xValue 의 값이 layoutWidth 의 두배라면 다음달로 스크롤 했다는 뜻입니다. 이렇게 이벤트에 따라 어디로 스크롤했는지 확인하고, 해당하는 달로 currentDate을 업데이트 시켜준 다음에 캘린더가 중간으로 스크롤 될 수 있도록 설정해 줍니다.

renderEvent()

저희가 마지막으로 볼 함수는 renderEvent()라는 함수 입니다. 이 함수는 이벤트가 저장된 날짜를 선택했을 경우, 그에 해당되는 이벤트를 텍스트로 캘린더 아래에 출력해줍니다.

Contribute to Calendar Carousel!

Dooboo-UI 는 현재 오픈소스로 진행되고 있는 프로젝트 입니다. 누구나 이코드를 받아 PR Request를 보내고 새로운 기능을 추가할 수 있습니다. 그러니 많이 참여해주시길 바랍니다 :)

  • web ScrollView feature
  • select Day range
  • add events
  • customize events

그럼 이걸로 마치겠습니다. 질문있으면 더 물어보세요! 감사합니다 :)

--

--