[달력 만들기 toy project] ① table tag 사용, 이번 달 날짜 출력

이민선(Jasmine)·2023년 4월 22일
0
post-thumbnail

달력 만들기 토이 프로젝트 시작!

예전부터 나의 프로그래밍 수준이 일정 수준 이상이 되면 나만의 달력을 한번쯤 만들어보고 싶었다. 알고리즘 실력이 어느 정도 뒷받침 되어야 하기도 하고, 상태 관리도 능숙하게 다룰 줄 알아야 하기 때문이다. 물론 검색해보니 Pikaday, air-datepicker와 같은 소스코드가 github에 이미 많지만, 라이브러리를 이용하여 원하는 결과만 짜잔~하고 구경하는 것이 나의 목표는 아니다. 그건 예를 들어 급할 때 언제든지 할 수 있을 것이다. 내가 원하는 기능을 내 손으로 직접 구현해보며 실력을 향상시키는 게 나의 목표이다.

특히 최근에 Redux를 배우기 시작했는데 상태 관리 연습으로도 아주 좋은 토이 프로젝트가 될 수 있을 것 같다.

고럼 본격적으로 시작해보자ㅏㅏ~~~ (둑은둑은)

table tag를 사용해보자.

일단 2023, 4월, 일월화수목금토만 띄워둔 mock을 만들고 시작!

처음에는 잘 모르고 div, span 태그로 요일을 띄웠지만, 나중에 table tag를 알게되어서 사용법을 익히고 고쳐보았다.

이 때 세운 전반적인 구조는 아래와 같다.

    // 달력 구조
    <Table> // styled.table
      <YearWrapper> // styled.caption
        <Year/> // styled.span
      </YearWrapper>
      <MonthWrapper> // styled.caption
        <Month/> // styled.span
      </MonthWrapper>
       <DaysContainer> // styled.thead
      <tr> // tr 태그
        {days.map((day, idx) => <Day key={idx}>{day}</Day>)} // styled.th
      </tr>
       </DaysContainer>
      <DatesContainer> // styled.tbody
      </DatesContainer> // <- 이 안에 날짜 알고리즘 들어감.
    </Table>

물론 관심사 분리를 위해 실제 코드에서는 파일을 분리했지만, 이 포스팅에서는 개요를 한 눈에 보기 좋게 표현해보고 싶었다. 그래서 위와 같이 대략적 뼈대를 그려보았다. table tag를 사용하는 게 div, span으로 띄워주는 것보다 semantic하다고 한다. 그래서 기존의 div 지옥 코드를 과감히 위와 같이 바꿔주었다.(일단 styled-component는 생략하고 각 컴포넌트 오른쪽에 태그 종류를 주석으로 남겼다.)

이제 DatesContainer 내부에 날짜를 표시해주는 알고리즘을 구현하는 코드를 작성해보아야 한다. new Date()이 아마도 이번 토이 프로젝트에서 가장 많이 사용하는 메서드가 되지 않을까 싶다.

이번 달 날짜

처음에 2차원 배열을 만들 때 작성했던 알고리즘은 아래와 같다.

const date = new Date();
  const year = date.getFullYear();
  const month = date.getMonth();

  // 이번 달 1일이 무슨 요일인지 구함
  const firstDayOfMonth = new Date(year, month, 1).getDay();
  // 이번 달이 며칠까지 있는지 구함
  const lastDateOfMonth = new Date(year, month + 1, 0).getDate();

  // 날짜를 채워줄 2차원 빈 배열 선언
  let daysOfThisMonth = [...Array(6)].map((_) => [...Array(7)].map(() => 0));
  let day = firstDayOfMonth;
  // 이번 달 1일에 해당하는 원소에 1을 할당
  daysOfThisMonth[0][day] = 1;

  for (let i = 0; i < daysOfThisMonth.length; i++) {
    for (let j = 0; j < 7; j++) {
      if (daysOfThisMonth[i][j - 1]) {
        daysOfThisMonth[i][j] = daysOfThisMonth[i][j - 1] + 1;
      } else if (daysOfThisMonth[i - 1]?.[6]) {
        daysOfThisMonth[i][j] = daysOfThisMonth[i - 1][6] + 1;
      }
      if (daysOfThisMonth[i][j] === lastDateOfMonth) break;
    }
  }

  console.log(daysOfThisMonth);

new Date() 내부에 연, 월, 일을 인자로 전달하면 해당 날짜의 Date 객체가 된다. 이를 이용하여 먼저

  • 이번 달 1일이 무슨 요일인지 구함 → getDay() 사용하여 firstDayOfMonth 변수에 담아둠.
  • 이번 달 마지막 날의 날짜를 구함 → getDate() 사용하여 lastDateOfMonth 변수에 담아둠.

다음으로 2차원 빈 배열을 선언하여 2중 for문으로 날짜들을 담아두었다.
2중 for문 내부의 중요 로직은 아래와 같다.

  • 만약 현재 행(row)에서 현재 원소 바로 이전 인덱스에 날짜가 존재한다면, 이번 원소는 이전 인덱스에 있는 숫자 + 1이다. 날짜는 매일 1씩 증가하니까!
  • 일요일을 고려하면 위의 로직만으로는 부족하다. 위의 조건문을 통과를 못하면 일요일 아니면 첫번째주인 케이스이다. 일요일인 경우에만 어제보다 날짜가 1만큼 증가할 수 있도록 else if에 조건문을 달아주었다.
  • 이렇게 쭉 1씩 증가하다가 미리 구해둔 lastDateOfMonth가 되면 break를 걸어서 32, 33 이런 숫자가 나오지 않도록 한다.

이렇게 로직을 짜고 2중 for문을 출력해보면

4월의 날짜가 담긴 2차원 배열이 나온다!!
이제 map을 이용하여 화면에 띄워보자.

  const now = Date.now();
  const today = date.getDate();
  const showDates = daysOfThisMonth.map((week, i) => (
    <tr key={now + i}>
      {week.map((date, j) => (
        <EachDate key={now + j} today={today}>
          {date ? date : null}
        </EachDate>
      ))}
    </tr>
    ));

여기서 EachDate는 th 태그이다.

짜잔!!! 띄워졌다!!

이제 화면에 띄웠으니 간격을 조절해주자.

이번 달 달력 완성..!

일 것 같았지만 보통 달력에 저번달이랑 다음달 날짜도 흐린색 글씨로 있쟈냐??
추후 포스팅에서 styled-components로 month에 따라 글자색을 다르게 만드는 것도 한 번 구현해봐야겠다 ㅎㅎㅎ

그런데... 잘 뜨는 줄만 알고 좋아했는데 Warning 메시지가 뜨는 걸..?

Warning: validateDOMNesting(...): <th> cannot appear as a child of <thead>.

기존의 th태그들을 tr 태그로 묶어주었더니 warning이 사라졌다.
table의 head 부분을 만들 때에는 thead의 자식으로 tr, tr의 자식으로 th가 들어가야 한단다.

const Day = styled.th`
  font-size: 40px;
  width: 9.8vw;
  color: white;
  text-shadow: 2px 2px 0.5px rgba(0, 0, 0, 0.5);
`;

const DaysContainer = styled.thead`
  position: absolute;
  margin-bottom: 290px;
`;
.
.
.
 const days = ["일", "월", "화", "수", "목", "금", "토"];
  const showDays = days.map((day, idx) => <Day key={idx}>{day}</Day>);
   return (
       <DaysContainer>
      <tr>{showDays}</tr> // tr 태그로 묶어주자!!
    </DaysContainer>
   );

오케이 여기까지 이번 달 날짜는 잘 출력이 된다.

여기까지는 많이 어렵지 않았다.
다음 포스팅에서는 마지막 행 빈 곳에 다음 달 날짜를, 첫 행 빈 곳에 저번 달 날짜를 구해서 채워보려고 한다. 이건 좀 쉽지 않을 것 같은 느낌적인 느낌..?

화이팅!!!

(++ 미래에서 왔습니다. 삽질 많이 합니다.)

profile
기록에 진심인 개발자 🌿

0개의 댓글