[ 개인플젝 ] 홈 - dayjs로 캘린더 그리기 기록
![[ 개인플젝 ] 홈 - dayjs로 캘린더 그리기 기록](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog-custom%3Ftitle%3D%255B%25EA%25B0%259C%25EC%259D%25B8%25ED%2594%2584%25EB%25A1%259C%25EC%25A0%259D%25ED%258A%25B8%255D%2BSTREAKLY%26tag%3DTemplate%2B1%26description%3D%25EC%259D%25B8%25EC%25A6%259D%2B%25EC%2595%25B1%2B%25EA%25B0%259C%25EC%259D%25B8%2B%25ED%2594%2584%25EB%25A1%259C%25EC%25A0%259D%25ED%258A%25B8%253A%2B%25ED%2599%2588%2B-%2Bdayjs%25EB%25A1%259C%2B%25EC%25BA%2598%25EB%25A6%25B0%25EB%258D%2594%2B%25EA%25B7%25B8%25EB%25A6%25AC%25EA%25B8%25B0%2B%25EA%25B8%25B0%25EB%25A1%259D%26template%3D3%26backgroundImage%3Dhttps%253A%252F%252Fsource.inblog.dev%252Fog_image%252Fdefault.png%26bgStartColor%3D%2523ffffff%26bgEndColor%3D%2523ffffff%26textColor%3D%2523000000%26tagColor%3D%2523000000%26descriptionColor%3D%2523000000%26logoUrl%3Dhttps%253A%252F%252Fsource.inblog.dev%252Flogo%252F2025-12-24T06%253A07%253A46.034Z-ddf15b75-3608-41bd-8914-cd5d2d9efc83%26blogTitle%3D&w=3840&q=75)
난 개인프로젝트로 시즌랭킹 인증앱을 작업중이다.
오늘은 홈 화면을 작업한다
홈에서 요하는 기능
상단
운동하기버튼을 누르면 인증 선택 버튼이 그 위에뜨며, 다른 영역을 선택시 인증 선택 버튼이 사라진다캘린더가 그려져 있고 인증한 날짜에는 날짜 대신 사진이 온다
프로그레스박스에는 시즌에 맞는 날짜를 박스로 그리고 인증한 날만 색이 칠해진다 (깃헙 잔디 따라함)
1. 버튼 모달 출력 후 다른 영역 누를시 사라짐
나는 웹을 작업할 때 이런 백그라운드에 이벤트가 있는 작업들을 할때 그냥 백에다가 전체 화면을 투명하게 띄우고 거기에 이벤트를 넣는 편이었다.
하지만 RN에서는 TouchableWithoutFeedback 을 이용하면 편하게 작업할 수 있다.
TouchableWithoutFeedback은 시각적 피드백 없이 단순히 터치 이벤트만 처리하고 싶을 때 사용하며 보통은 키보드 비활성화시 사용한다
아래의 형태로 사용했다. 버튼은 HomeTopContents 내에 있고 TouchableWithoutFeedback가 부모로서 감싸고 있는 형태로 닫는 함수를 가지고 있다
<ScrollView showsVerticalScrollIndicator={false} bounces={false}>
<TouchableWithoutFeedback onPress={모달닫는 함수}>
<View >
<HomeTopContents
authMethodSelectOpen={authMethodSelectOpen}
/>⚠️ 여기서 하나 기록하고 넘어가자면 TouchableWithoutFeedback 내에 ScrollView가 들어올경우 터치 이벤트가 무시되는 것처럼 보여지게 된다
둘을 부모자식 관계로 사용해야하는 관계는 되도록 피하고
관계성 없이 영역을 따로 가지던 아니면 ScrollView를 부모로 가져야 두 기능 다 수행할 수 있다.
2. dayJS로 캘린더 모양 그리기
캘린더는 일요일 → 토요일 순으로 날짜가 흘러간다
[ 일, 월, 화, 수, 목, 금, 토 ] 의 7개의 컬럼을 가진다
1일은 요일에 맞는 날부터 시작한다
1일 앞에 남는 요일이 있다면 빈칸으로 채운다
마찬가지로 마지막날(31일 등)뒤에 남는 요일이 있다면 빈칸으로 채운다
위의 조건을 가지고 그리면 된다
const createCalendarDays = (year: string, month: string) => {
const daysInMonth = dayjs(`${year}-${month}`).daysInMonth();
const firstDayOfMonth =
dayjs(`${year}-${month}-01`).startOf('month');
const calendarDays: CalendarDay[] = [];
for (let i = 0; i < firstDayOfMonth.day(); i++) {
calendarDays.push({ type: 'empty', date: null });
}
for (let i = 1; i <= daysInMonth; i++) {
const date = dayjs(`${year}-${month}-${i}`);
calendarDays.push({
type: 'day',
date: i,
isCurrentMonth: true,
year: date.year(), // 2025
month: date.month() + 1, // 1~12 (주의)
day: date.date(), // 1~31
fullDate: date.format('YYYY-MM-DD'),
});
}
const remainder = calendarDays.length % 7;
if (remainder !== 0) {
const emptyCount = 7 - remainder;
for (let i = 0; i < emptyCount; i++) {
calendarDays.push({ type: 'empty', date: null });
}
}
return calendarDays;
};daysInMonth는 해달 달의 일 수
firstDayOfMonth.day()는 1일의 요일dayjs().day()는 0(일) ~ 6(토)으로 반환된다
calendarDays그려질 배열을 준비하고
날짜 앞쪽 빈칸 채우기
0부터 1일의 요일 만큼 빈칸을 채운다
for (let i = 0; i < firstDayOfMonth.day(); i++){}날짜를 채운다
for (let i = 1; i <= daysInMonth; i++) {}날짜 뒷쪽 빈칸 채우기
remainder에 현재까지 쌓인 날짜를 7로 나눈 나머지를 할당한다remainder값이 0이 아닐경우에 뒤에 영역이 남았다고 판단
7 - remainder 값을 반복하여 빈칸을 채운다
const remainder = calendarDays.length % 7; if (remainder !== 0) { const emptyCount = 7 - remainder; for (let i = 0; i < emptyCount; i++) {} }그려질 데이터가 쌓인
calendarDays를 리턴한다
이제 calendarDays는 빈칸과 날짜 모두를 가지고 있으므로 그대로 그리면 된다.
처음에는 FlatList를 이용해 그렸었다 하지만 홈은 ScrollView로 감싸져 있어 워닝이 발생했다. FlatList + FlatList을 사용할 수 있었지만 데이터가 많지 않아 굳이 FlatList를 고집할 이유가 없으므로 맵으로 그려줬다
{calendarDays.map((item, index) => (
<CalendarCell
item={item}
index={index}
totalLength={calendarDays.length}
recordMap={recordMap}
key={item.date ?? index}
/>
))}셀의 사이즈는
사용될 영역을 7로 나누어 7컬럼을 유지하도록 하고
난 4(세로) : 3(가로)의 비율로 그려지길 원했기 때문에 아래와 같이 사용하여 셀을 그렸다
const CELL_WIDTH = (width - 40 - inset * 6) / 7;
const CELL_HEIGHT = CELL_WIDTH * (4 / 3);