react 라이브러리 없이 달력 만들기

G-NOTE·2023년 8월 28일
0

React

목록 보기
26/27
post-thumbnail

몇 달 전 개인 프로젝트(가계부 웹서비스) 제작 중 달력이 필요했는데 시간이 촉박하고 구현도 까다로웠던 기억이 있어 react-calendar라이브러리를 사용했었다.
하지만 라이브러리 특성상 커스터마이징이 힘들기도 했고, 얼마 전 이전 달로 이동할 때 월별 총 금액이 이상하게 계산되는 이슈를 발견해서 해결하자니 이벤트핸들러를 뜯어보기가 더 까다로웠다.
그래서 내가 만들어보기로..ㅎ

달력 로직 구상하기

달력 로직 고민

(고민의 흔적)

달력의 한 페이지엔 이전 달 일부 - 내가 선택한 달 전체 - 다음 달 일부 가 들어간다.

날짜 채우는 방식 (선택한 달의 총 일수 = n, 시작 요일 = start)
0. 달력은 7의 배수로 모두 채워져야 한다. (28일 / 35일 / 42일)
1. prev(노출되는 이전 달 날짜 수) : 일요일 ~ 선택한 달의 시작 요일 전날까지 일수 (시작 요일이 일요일일 경우 없음)
➡️ prev = start
2. next(노출되는 다음 달 날짜 수) : 선택한 달의 마지막 요일 ~ 토요일까지 일수 (마지막 요일이 토요일일 경우 없음)
➡️ r = (n + start) % 7
➡️ r === 0 : next = 0
➡️ r !== 0 : next = 7 - r (7일 중 마지막 주에 채워진 요일 제외한 일수)
*n + start : 달력의 첫번째 일요일부터 시작해야하기 때문

코드

const Calendar = () => {
  const [newDate, setNewDate] = useState<Date>(new Date());
  const [year, setYear] = useState(0);
  const [month, setMonth] = useState(0);
  const [date, setDate] = useState(0);
  const [lastDate, setLastDate] = useState<number>(0);
  const [lastDatePrev, setLastDatePrev] = useState<number>(0);
  const [firstDay, setFirstDay] = useState<number>(0);
  const [displayDate, setDisplayDate] = useState("");
  const [openSelect, setOpenSelect] = useState(false);

  useEffect(() => {
    const yy = newDate.getFullYear();
    const mm = newDate.getMonth();
    const dd = newDate.getDate();

    setYear(yy);
    setMonth(mm);
    setDate(dd);

    setLastDate(new Date(yy, mm + 1, 0).getDate());
    setLastDatePrev(new Date(yy, mm, 0).getDate());
    setFirstDay(new Date(yy, mm, 1).getDay());
    setDisplayDate(`${yy}${mm + 1}${dd}`);
  }, [newDate]);

  const handleDateArr = (
    target: "prev" | "cur" | "next",
    lastDate: number,
    lastDatePrev: number,
    firstDay: number
  ) => {
    let res: number[] = [];

    const r = (lastDate + firstDay) % 7; // remainder
    const prev = firstDay; // 이전 달 표기 일수
    const next = r === 0 ? 0 : 7 - r; // 다음 달 표기 일수

    if (target === "prev") {
      res = Array.from({ length: prev }, (_, i) => lastDatePrev - i).sort(
        (a, b) => a - b
      );
    } else if (target === "cur") {
      res = Array.from({ length: lastDate }, (_, i) => i + 1);
    } else if (target === "next") {
      res = Array.from({ length: next }, (_, i) => i + 1);
    }

    return res;
  };

  const handleChange = (type: "prev" | "next", target: "year" | "month") => {
    if (type === "prev") {
      target === "month"
        ? setNewDate(new Date(year, month - 1, 1))
        : setNewDate(new Date(year - 1, month, 1));
    } else {
      target === "month"
        ? setNewDate(new Date(year, month + 1, 1))
        : setNewDate(new Date(year + 1, month, 1));
    }
  };

  const handleSelect = (target: number) => {
    const mm = (month + 1).toString().padStart(2, "0");
    const dd = target.toString().padStart(2, "0");
    const selectedDate = `${year}-${mm}-${dd}`;
  };

  const handleChangeDirectly = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const { value, name } = e.target;
    if (name === "year") setNewDate(new Date(Number(value), month - 1, 1)); 
    // date = 1 : 오늘이 30일일 때 2월로 이동하면 다음달로 넘어감 (없는 값)
    if (name === "month") setNewDate(new Date(year, Number(value) - 1, 1)); 
    // date = 1 : 오늘이 30일일 때 2월로 이동하면 다음달로 넘어감 (없는 값)
  };

  return (
    <StCalendar>
      <StHeader>
        <h2>Calendar</h2>
        <h3>{displayDate}</h3>
        <StBtnContainer>
          <button onClick={() => handleChange("prev", "year")}>
            PREV YEAR
          </button>
          <button onClick={() => handleChange("prev", "month")}>
            PREV MONTH
          </button>
          <button onClick={() => handleChange("next", "month")}>
            NEXT MONTH
          </button>
          <button onClick={() => handleChange("next", "year")}>
            NEXT YEAR
          </button>
        </StBtnContainer>
      </StHeader>
      <StBody>
        <StDayList>
          {DAY_LIST.map((val) => {
            return <li key={val}>{val}</li>;
          })}
        </StDayList>
        <StDateList>
          {handleDateArr("prev", lastDate, lastDatePrev, firstDay).map(
            (val) => (
              <StPrevDate
                key={`prev-${val}`}
                onClick={() => handleChange("prev", "month")}
                >
                <span>{val}</span>
              </StPrevDate>
            )
          )}
          {handleDateArr("cur", lastDate, lastDatePrev, firstDay).map(
            (val) => (
              <StCurDate
                key={`current-${val}`}
                onClick={() => handleSelect(val)}
                >
                <span>{val}</span>
              </StCurDate>
            )
          )}
          {handleDateArr("next", lastDate, lastDatePrev, firstDay).map(
            (val) => (
              <StNext
                key={`next-${val}`}
                onClick={() => handleChange("next", "month")}
                >
                <span>{val}</span>
              </StNext>
            )
          )}
        </StDateList>
      </StBody>
    </StCalendar>
  );
};

export default Calendar;

calendar

달력 만들기 성공!
코드 더 다듬어서 프로젝트에 넣어야겠다.

후기

2년 전쯤 처음 자바스크립트, 리액트 공부할 때 달력을 만들려다 (몇 시간을 고민하고) 실패했었다. 그래서 처음 프로젝트 만들 때에도 라이브러리를 사용했던 것..
지금은 한 20분 정도 로직 고민해보고 금방 만들었다. 뿌듯 😎

profile
FE Developer

0개의 댓글