React에서 사용자 정의 캘린더 구성 요소를 구축하는 방법

소개



많은 웹 애플리케이션은 달력을 사용하여 날짜를 관리해야 하지만 대다수의 기사/튜토리얼은 항상 타사 라이브러리를 사용합니다. 그것이 나쁘다는 것이 아니라 반대로 응용 프로그램의 프로토타입을 만드는 데 많은 도움이 되지만 디자인과 요구 사항이 맞춤형인 경우 개발자에게 매우 어렵습니다.

같은 이유로 오늘 기사에서는 나중에 확장하여 더 많은 기능을 추가할 수 있는 기본 구성 요소를 만드는 방법을 알려 드리겠습니다.

우리는 무엇을 사용할 것입니까?



오늘은 제가 가장 좋아하는 두 가지 라이브러리를 사용할 것입니다.

  • Day.js - 날짜를 조작, 구문 분석 및 검증하는 데 도움이 되는 라이브러리입니다
  • .

  • Stitches - 경이로운 개발 경험이 있는 css-in-js 스타일링 라이브러리

  • 이들은 이 문서에서 사용된 라이브러리이지만 동일한 결과를 다른 라이브러리에서도 쉽게 복제할 수 있다는 점을 명심하십시오.

    전제 조건



    이 자습서를 따르려면 다음이 필요합니다.
  • React 기본 이해
  • TypeScript에 대한 기본 이해

  • TypeScript에 익숙하지 않더라도 데이터 유형을 "무시"하고 코드가 JavaScript와 정확히 동일하기 때문에 괜찮습니다.

    시작하기



    첫 번째 단계로 프로젝트 디렉토리를 생성하고 해당 디렉토리로 이동합니다.

    yarn create vite react-calendar-ts --template react-ts
    cd react-calendar-ts
    


    이제 필요한 종속성을 설치할 수 있습니다.

    yarn add dayjs react-icons @stitches/react @fontsource/anek-telugu
    


    그런 다음 styles.ts라는 파일에 html 요소의 스타일을 만듭니다.

    // @/src/styles.ts
    import { styled } from "@stitches/react";
    
    export const MainWrapper = styled("div", {
      width: 240,
      borderRadius: 10,
      padding: 20,
      backgroundColor: "white",
      boxShadow: "-6px 7px 54px -24px rgba(0,0,0,0.5)",
      fontFamily: "Anek Telugu",
    });
    
    export const CalendarHeaderWrapper = styled("div", {
      display: "flex",
      alignItems: "center",
      justifyContent: "space-between",
    });
    
    export const WeekDaysWrapper = styled("div", {
      display: "flex",
      flexDirection: "row",
      justifyContent: "space-between",
    });
    
    export const WeekDayCell = styled("div", {
      height: 30,
      width: 30,
      margin: 2,
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      color: "#9BA4B4",
    });
    
    export const CalendarContentWrapper = styled("div", {
      display: "flex",
      flexDirection: "row",
    });
    
    export const CalendarDayCell = styled("div", {
      height: 30,
      width: 30,
      display: 'flex',
      alignItems: "center",
      justifyContent: "center",
      borderRadius: 6,
      margin: 2,
    
      variants: {
        variant: {
          default: {
            color: "#1B1B2F",
          },
          today: {
            color: "#E43F5A",
          },
          nextMonth: {
            color: "#DAE1E7",
          },
        },
      },
    });
    


    생성된 요소의 스타일과 오늘의 변형이 이미 추가되었으므로 이제 오늘 기사의 모든 논리를 포함할 App.tsx 구성 요소 작업을 시작할 수 있습니다.

    // @/src/App.tsx
    import "@fontsource/anek-telugu";
    import { useCallback, useMemo, useState } from "react";
    import dayjs, { Dayjs } from "dayjs";
    import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md";
    
    import * as Styles from "./styles";
    
    export const App = () => {
      // logic goes here...
      return (
        // JSX goes here...
      )
    }
    


    위의 코드에서 우리는 구성 요소를 만드는 데 필요한 가져오기를 수행했습니다. 다음 단계는 현재 날짜, 월 1일, 월 1일의 1일이 무엇인지 세 가지 중요한 값을 획득하는 것입니다.

    // @/src/App.tsx
    import "@fontsource/anek-telugu";
    import { useCallback, useMemo, useState } from "react";
    import dayjs, { Dayjs } from "dayjs";
    import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md";
    
    import * as Styles from "./styles";
    
    export const App = () => {
      const [selectedDate, setSelectedDate] = useState<Dayjs>(dayjs());
    
      const currentDay = useMemo(() => dayjs().toDate(), []);
    
      const firstDayOfTheMonth = useMemo(
        () => selectedDate.clone().startOf("month"),
        [selectedDate]
      );
    
      const firstDayOfFirstWeekOfMonth = useMemo(
        () => dayjs(firstDayOfTheMonth).startOf("week"),
        [firstDayOfTheMonth]
      );
    
    
      // more logic goes here...
      return (
        // JSX goes here...
      )
    }
    


    이 세 가지 날짜를 얻었으므로 이제 두 개의 함수를 만들어야 합니다. 하나는 매월 첫 번째 날을 생성하는 함수이고 두 번째 함수는 첫 번째 요일을 고려하여 해당 월의 주를 생성하는 역할을 합니다. .

    // @/src/App.tsx
    import "@fontsource/anek-telugu";
    import { useCallback, useMemo, useState } from "react";
    import dayjs, { Dayjs } from "dayjs";
    import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md";
    
    import * as Styles from "./styles";
    
    export const App = () => {
      const [selectedDate, setSelectedDate] = useState<Dayjs>(dayjs());
    
      const currentDay = useMemo(() => dayjs().toDate(), []);
    
      const firstDayOfTheMonth = useMemo(
        () => selectedDate.clone().startOf("month"),
        [selectedDate]
      );
    
      const firstDayOfFirstWeekOfMonth = useMemo(
        () => dayjs(firstDayOfTheMonth).startOf("week"),
        [firstDayOfTheMonth]
      );
    
      const generateFirstDayOfEachWeek = useCallback((day: Dayjs): Dayjs[] => {
        const dates: Dayjs[] = [day];
        for (let i = 1; i < 6; i++) {
          const date = day.clone().add(i, "week");
          dates.push(date);
        }
        return dates;
      }, []);
    
      const generateWeek = useCallback((day: Dayjs): Date[] => {
        const dates: Date[] = [];
        for (let i = 0; i < 7; i++) {
          const date = day.clone().add(i, "day").toDate();
          dates.push(date);
        }
        return dates;
      }, []);
    
    
      // more logic goes here...
      return (
        // JSX goes here...
      )
    }
    


    이제 필요한 날짜와 해당 월의 주를 생성하는 데 필요한 기능이 있으므로 이제 useMemo() 후크를 사용하여 위에서 언급한 날짜의 변경 사항에 반응하고 해당 월의 일과 주를 생성해야 합니다. 다음과 같이 동일한 내용을 메모합니다.

    // @/src/App.tsx
    import "@fontsource/anek-telugu";
    import { useCallback, useMemo, useState } from "react";
    import dayjs, { Dayjs } from "dayjs";
    import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md";
    
    import * as Styles from "./styles";
    
    export const App = () => {
      const [selectedDate, setSelectedDate] = useState<Dayjs>(dayjs());
    
      const currentDay = useMemo(() => dayjs().toDate(), []);
    
      const firstDayOfTheMonth = useMemo(
        () => selectedDate.clone().startOf("month"),
        [selectedDate]
      );
    
      const firstDayOfFirstWeekOfMonth = useMemo(
        () => dayjs(firstDayOfTheMonth).startOf("week"),
        [firstDayOfTheMonth]
      );
    
      const generateFirstDayOfEachWeek = useCallback((day: Dayjs): Dayjs[] => {
        const dates: Dayjs[] = [day];
        for (let i = 1; i < 6; i++) {
          const date = day.clone().add(i, "week");
          dates.push(date);
        }
        return dates;
      }, []);
    
      const generateWeek = useCallback((day: Dayjs): Date[] => {
        const dates: Date[] = [];
        for (let i = 0; i < 7; i++) {
          const date = day.clone().add(i, "day").toDate();
          dates.push(date);
        }
        return dates;
      }, []);
    
    
      const generateWeeksOfTheMonth = useMemo((): Date[][] => {
        const firstDayOfEachWeek = generateFirstDayOfEachWeek(
          firstDayOfFirstWeekOfMonth
        );
        return firstDayOfEachWeek.map((date) => generateWeek(date));
      }, [generateFirstDayOfEachWeek, firstDayOfFirstWeekOfMonth, generateWeek]);
    
      return (
        // JSX goes here...
      )
    }
    


    로직이 완료되면 generateWeeksOfTheMonth 변수에서 얻은 데이터를 매핑하기만 하면 되지만 월 사이를 탐색하고 선택한 날짜를 업데이트해야 한다는 점도 고려해야 합니다. 달력에서 현재 날짜를 시각적으로 식별해야 하는 것과 같습니다. 다음과 같이 할 수 있습니다.

    // @/src/App.tsx
    import "@fontsource/anek-telugu";
    import { useCallback, useMemo, useState } from "react";
    import dayjs, { Dayjs } from "dayjs";
    import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md";
    
    import * as Styles from "./styles";
    
    export const App = () => {
      // hidden for simplicity...
    
      return (
        <Styles.MainWrapper>
          <Styles.CalendarHeaderWrapper>
            <h3>{selectedDate.clone().format("MMM YYYY")}</h3>
            <div>
              <MdKeyboardArrowLeft
                size={25}
                onClick={() => setSelectedDate((date) => date.subtract(1, "month"))}
              />
              <MdKeyboardArrowRight
                size={25}
                onClick={() => setSelectedDate((date) => date.add(1, "month"))}
              />
            </div>
          </Styles.CalendarHeaderWrapper>
          <Styles.WeekDaysWrapper>
            {generateWeeksOfTheMonth[0].map((day, index) => (
              <Styles.WeekDayCell key={`week-day-${index}`}>
                {dayjs(day).format("dd")}
              </Styles.WeekDayCell>
            ))}
          </Styles.WeekDaysWrapper>
          {generateWeeksOfTheMonth.map((week, weekIndex) => (
            <Styles.CalendarContentWrapper key={`week-${weekIndex}`}>
              {week.map((day, dayIndex) => (
                <Styles.CalendarDayCell
                  key={`day-${dayIndex}`}
                  variant={
                    selectedDate.clone().toDate().getMonth() !== day.getMonth()
                      ? "nextMonth"
                      : dayjs(currentDay).isSame(day, "date")
                      ? "today"
                      : "default"
                  }
                >
                  {day.getDate()}
                </Styles.CalendarDayCell>
              ))}
            </Styles.CalendarContentWrapper>
          ))}
        </Styles.MainWrapper>
      )
    }
    


    지금까지 기사를 따라했다면 브라우저에서 다음과 매우 유사한 결과를 얻을 수 있을 것입니다.



    다음 과제는 무엇입니까?



    이 점은 상당히 상대적이지만 다음 기능에 중점을 둘 것입니다.
  • 재사용 가능한 달력 구성 요소 생성
  • 구성 요소는 상태 비저장이어야 합니다
  • .
  • 하루 및 여러 날 선택
  • 여러 요일을 선택할 때 주말 포함 여부를 토글합니다
  • .

    결론



    늘 그렇듯이 기사가 마음에 드셨기를 바라며 기존 프로젝트에 도움이 되었거나 단순히 사용해 보고 싶으셨기를 바랍니다.

    기사에서 잘못된 부분을 발견했다면 댓글로 알려주시면 수정하겠습니다. 마치기 전에 이 기사의 소스 코드에 액세스하려면 github 저장소에 대한 링크here를 남겨둡니다.

    좋은 웹페이지 즐겨찾기