토스 Slash 영상을 보면서 내 프로젝트에도 적용해볼 수 있지 않을까 하는 부분이 여러 있었는데 이번에는 useCalendar 훅을 만들어서 한번 적용해보고자 한다.
토이 프로젝트로 Calendeok
이라는 달력을 만들었는데 라이브러리처럼 만드는 것을 목표로 했기 때문에 어떻게 하면 누구나 쉽게 가져다 쓸 수 있을까? 하는 고민을 많이 했었다.
마침 토스 영상에서 useCalendar 로 훅으로 만들어 calendar 를 구현한 샘플 코드를 보고 아 ! 이렇게 만들면 CSS 를 신경쓰지 않고 달력을 구현할 수 있겠구나
하는 생각이 들었고, 기존 코드를 리팩토링 해보기로 했다.
영상에도 나왔듯 핵심은 달력을 구성하는 데이터는 변하지 않지만 달력의 UI 는 언제든지 변경 될 수 있다 는 점이다.
달력을 구성하는데 필요한 데이터를 useCalendar hooks 에 위임해서 데이터와 UI 를 구분하고 오로지 데이터에만 집중해서 모듈화한다.
바로 이런 패턴을 Headless
라고 하는데, 한가지 문제에만 집중하기 때문에 더 많은 곳에서 사용할 수 있고 변경으로부터 격리시킬 수 있다.
기존에 라이브러리를 사용하면서 불편했던 커스텀 문제를 이를 통해 해결할 수 있겠구나 싶었다.
UI 라이브러리를 사용하면서 괜찮은 디자인을 찾아보기가 어렵고, 커스텀을 하려고 해도 막상 내가 원하는대로 커스텀 하는것도 상당히 어려웠는데,
useCalendar 를 사용해서 달력 데이터만 편하게 가져다 쓰고 스타일링은 각자 원하는대로 하면 좋을 것 같다는 생각이 들었다.
우선 useCalendar 로 구현한 달력 완성본부터 보자면 아래와 같다.
약간의 CSS 를 추가해서 보기 좋게 만들었다.
토스 예시 코드에서는 useCalendar 로 headers, body, view 를 가져와 화면에 그렸는데
나는 조금 더 세분화했다.
prevController, nextController 👉 이전 월, 다음 월로 이동할 수 있는 함수
curMonth, curYear 👉 선택한 연도와 월
weeks 👉 요일을 나타내는 값
body 👉 달력의 날짜
토스 버전
const { headers, body, view } = useCalendar();
내가 만든 버전
const { prevController, nextController, curMonth, curYear, weeks, body } = useCalendar();
아래와 같이 useCalendar 로 값을 불러와서 사용하면 된다.
import dayjs from 'dayjs';
import useCalendar from './hooks/useCalendar';
import { useState } from 'react';
function App() {
const { prevController, nextController, curMonth, curYear, weeks, body } = useCalendar();
const [selected, setSelected] = useState<Date>(new Date());
const selectedDate = dayjs(selected).format('YYYY-MM-DD');
const handleDate = (date: Date) => {
setSelected(date);
};
return (
<>
<div className="container">
<div className="date">{selectedDate}</div>
<div className="header">
<div>
{curYear}년 {curMonth + 1}월
</div>
</div>
<div className="controller">
<button onClick={prevController}>이전 달</button>
<button onClick={nextController}>다음 달</button>
</div>
<table className="table">
<thead>
<tr>
{weeks.ko.map((week, index) => (
<th key={index}>{week}</th>
))}
</tr>
</thead>
<tbody className="cell">
{body.map((rows, index) => (
<tr key={index}>
{rows.map((row) => (
<td key={row.value} className={`dateCell ${selected === row.date && 'isSelected'}`}>
<button onClick={() => handleDate(row.date)}>{row.date.getDate()}</button>
{dayjs().isSame(row.date, 'day') && <div>Today</div>}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</>
);
}
export default App;
이전 코드는 달력 데이터와 UI 가 함께 작성되어 있어서 스타일 변경이 자유롭지 못했다.
예를들어 아래와 같은 요구사항이 들어오면 기존 코드에서는 달력 전체를 건드려야 했다.
그러나 useCalendar 로 데이터와 UI 를 구분해 작성하니 controller 위치만 조정하니 쉽게 구현이 가능했다.
연도와 월을 가운데로 옮겨주시고, 버튼을 양쪽에 두고 싶어요.
그리고 버튼은 손가락 이모티콘으로 바꿔주세요
기존 | 변경 요청 |
---|---|
![]() | ![]() |
이번에 토스 영상을 보고 데이터와 UI 를 구분해서 코드를 작성하는 방법에 대해 알게 되었고, 마침 토이 프로젝트로 달력을 만들어 놓은게 있어서 쉽게 젹용해볼 수 있었다.
평소에는 input 이나 button, modal 과 같은 것들을 hook 으로 만드는걸 당연하게 생각해왔는데, 달력도 마찬가지로 변하지 않는 기본 동작을 훅으로 만들 수 있다는 생각을 못했었다. 이번 경험을 통해 데이터와 UI를 분리하고, 훅을 사용하여 변하는 값과 변하지 않는 값의 구분을 명확히 하는 데에 대한 아이디어를 얻을 수 있었다.