
캘린더
일반적으로 캘린더를 사용하는 이유는 여러가지가 있겠지만 대부분 다음 기능을 필요로 할 것이다.
- 날짜를 확인하기 위함(완전 기본)
- 일정을 확인하기 위함
- 기타 등등
이번에 구현하려고 하는 기능은 캘린더의 근간이 되는 날짜 확인 기능을 구현할 예정이다. 캘린더는 굉장히 다양한 프로그램에 들어간다. 따라서 관련 라이브러리도 다양한데, 라이브러리를 사용하면 당장에는 편할 수 있지만 추가적인 기능을 넣기에는 처음부터 직접 구현하는게 편할거라 판단되어 기본 기능부터 내가 필요한 기능까지 구현할 예정이다. 우선 추가적인 기능은 이후에 포스팅 할 예정이며 일정을 확인할 수 있는 기능을 구현할 예정이다.
목표
현재 진행중인 팀 프로젝트(캠핑 정보 관련)에서 사용 될 일정 확인 캘린더
모임 일정과, 내가 정복했던 캠핑장에 대한 기록을 확인 할 수 있다.
기본 캘린더 UI 만들기
우선 캘린더에 어떤 기능을 넣든지 간에 기본이 되는 캘린터 틀을 제작해야한다. 1차 구현 목표는 모두가 알고 있는 캘린더의 모양대로 월 별 일자를 출력해주고, 이전 월, 다음 월로 이동할 수 있는 정도의 기능까지 구현하는 것을 목표로 한다. (아래 예시)

고민했던 것
월 기준 첫 번째로 표시되는 일자 와 마지막으로 표시되는 일자 를 어떻게 구할 것인가? (예시 사진을 보면 이전 월에 해당하는 26일이 시작, 다음 월에 해당하는 6일이 끝인 것을 볼 수 있다.)
이게 기본 캘린더 UI를 만들 때 가장 골치아팠던 것 중 하나이다. (물론 내 기준임 ...) 이 부분은 다음과 같이 구현했다.
- 현재 월의 첫 일자
(monthStart)를 구한다. - 첫 일자가 속한 주의 첫 일자
(dayStart)를 구한다. - 현재 월의 마지막 일자
(monthEnd)를 구한다. - 마지막 일자가 속한 주의 마지막 일자
(dayEnd)를 구한다.
즉, dayStart, dayEnd 를 구한 뒤 반복하며 출력하면 된다.
date-fns
하나 고백할 것이 있다. 라이브러리를 쓰지 않고 만든다고 했지만 Date자료형을 편하게 다루기 위한 라이브러리는 쓸 예정이다. 그리고 date-fns를 사용하기로 했다. JavaScript에서 Date 관련 라이브러리는 여러개 있지만(moment.js, day.js, date-fns 등) 성능적으로 우수하고 함수 단위로 기능이 잘 분리되어 있어 좀 더 직관적이고 쉽게 사용가능할 것 같아 선택했다.
npm install date-fns --save
# or
yarn add date-fns컴포넌트 구조
Calendar
├─CalendarHeader
└─CalendarTable
└─CalendarCellCalendar 컴포넌트
- state 핸들링(
currentMonth) - 데이터 패칭 및 가공
- 데이터 전달
1. state 핸들링 코드
오늘 일자를 currentMonth에 초기값으로 넣어준다.(오늘 일자를 기준으로 현재 월을 구함)
currentMonth를 핸들링(이전 월, 다음 월)할 수 있는 함수를 작성하고 CalendarHeader 컴포넌트에 전달한다.
// 현재 월을 초기값으로 지정
const [currentMonth, setCurrentMonth] = useState<Date>(new Date());
// 이전 월
const prevMonth = () => {
setCurrentMonth(subMonths(currentMonth, 1));
};
// 다음 월
const nextMonth = () => {
setCurrentMonth(addMonths(currentMonth, 1));
};현재 월을 구한 뒤 이전 월, 다음 월 등으로 state를 핸들링을 할 수 있고, 사용자의 일정 데이터를 불러와서 데이터를 가공(비즈니스 로직 따로 분리) 한다.
CalendarHeader 컴포넌트
- 이전 월, 다음 월로 가는 버튼
- 요일 출력 (일 ~ 토)
1. 요일 출력
표시 할 요일을 배열로 정의한다. 일, 월, 화 . . . 같은 형식으로 출력하기로 했다.
const days = ["일", "월", "화", "수", "목", "금", "토"]; <ul className="grid grid-cols-7">
{days.map((day) => (
<li key={day} className="flex w-full justify-center">
<span>{day}</span>
</li>
))}
</ul>2. 이전 월, 다음 월로 넘어가는 버튼 생성
버튼을 생성하고 Calendar 컴포넌트에서 Props로 전달한 prevMonth, nextMonth를 onClick 이벤트에 넣어준다. (버튼 디자인은 못본걸로 하는게 좋겠다.)
<div className="flex">
<div onClick={prevMonth}>{"<="}</div>
<div onClick={nextMonth}>{"=>"}</div>
</div>전체 코드
import React from "react";
import { format } from "date-fns";
type Props = {
currentMonth: Date;
prevMonth: () => void;
nextMonth: () => void;
};
const CalendarHeader = ({ currentMonth, prevMonth, nextMonth }: Props) => {
const days = ["일", "월", "화", "수", "목", "금", "토"];
return (
<div className="w-full">
<section className="flex">
<div className="">
<span className="">
{format(currentMonth, "yyyy")}년 {format(currentMonth, "M")}월
</span>
</div>
<div className="flex">
<div onClick={prevMonth}>{"<="}</div>
<div onClick={nextMonth}>{"=>"}</div>
</div>
</section>
<section>
<ul className="grid grid-cols-7">
{days.map((day) => (
<li key={day} className="flex w-full justify-center bg-slate-300">
<span>{day}</span>
</li>
))}
</ul>
</section>
</div>
);
};
export default CalendarHeader;그리고 추후에 캘린더 내용을 컨트롤 할 수 있는 기능(필터링이나 필터링, 그리고 필터링 등과 같은 기능들)이 생기면 CalendarHeader에 다 때려 박으면 좋을 것 같다.
CalendarTable 컴포넌트
dayStart와dayEnd를 구하고 그 기간 만큼 반복하며CalendarCell을 호출한다.
1. 현재 월의 시작날, 마지막날 구하기
const monthStart = startOfMonth(currentMonth);
const monthEnd = endOfMonth(currentMonth);2. 시작날, 마지막날 기준 주의 시작날(dayStart), 주의 마지막날(dayEnd)을 구하기
실제로 캘린더에는 현재 월에 해당하는 일자들만 표시되지 않고, 이전 월, 다음 월의 일자들까지도 일부 표시된다. 해당 기능을 위해 필요하다.
startOfWeek, endOfWeek 함수를 이용해 구현했다.
const dayStart = startOfWeek(monthStart); // monthStart 일자 기준 주의 시작날을 구함
const dayEnd = endOfWeek(monthEnd); // monthEnd 일자 기준 주의 마지막날을 구함3. dayStart ~ dayEnd까지 반복하며 CalendarCell 호출
날짜가 들어갈 배열을 하나 생성하고, 값을 넣어준다.
const days = [];
/** dayStart ~ dayEnd까지 반복 */
for (let i = dayStart; i <= dayEnd; i = addDays(i, 1)) {
days.push(i);
}4. CalendarCell 호출
days 배열을 반복하며 CalendarCell 컴포넌트를 호출한다.
각 Cell에 해당하는 day를 전달한다.
{days.map((day) => (
<CalendarCell
key={day.toISOString()}
day={day}
/>
))}1차 구현 완료
여기까지만 하면 완전 기본 캘린더 구현은 끝이다. 월을 변경 할 수 있고, 변경된 월 기준으로 날짜도 잘 렌더링 된다. 하지만 우리가 제작한 웹 사이트에 아무 기능도 들어있지 않은 캘린더를 사용 할 일은 없으니(99%) 지금까지 진행된 내용을 기준으로 디자인, 기능을 알아서 커스텀하면 되겠다.(1차 완성 예시)

다음 포스팅에서는 진행중인 프로젝트에 필요한 기능을 구현하는 것을 업로드 해보도록 해보겠습니다.
- 캘린더에
일정 표시 - 표시되는 일정은
후기(stamp),캠핑 모임(meet)등이 있음 - 후기 데이터는 당일, 모임 데이터는 기간(or 당일)