Skip to content

아키텍처

Yeonu Kim edited this page Aug 25, 2024 · 3 revisions

폴더 구조

웹사이트의 폴더 구조는 다음과 같습니다.

│─public: 정적 파일 저장 (css, image 등)
│  └─css
└─src
    ├─components: 각 페이지에 들어갈 컴포넌트의 view 저장
    │  ├─admin
    │  ├─common
    │  └─user
    ├─pages: 각각의 페이지 저장
    ├─states: state 정의 저장
    ├─utils
    └─viewModel: custom hook 저장

MVVM 아키텍처

MVVM 아키텍처를 사용하여 폴더를 정리하였습니다.

M(Model)

각각의 state를 정의합니다. state 폴더에서 model을 담당하고 있습니다. 현재는 zustand를 사용하여 state를 관리하고 있습니다.

// Model 예시 코드(attendStore)
import create from 'zustand';

const useAttendStore = create((set) => ({
// attend state에 대해 정의합니다.
  attend: {
    userId: null,
    sessionId: null,
    attendIdx: 0,
    status: false,
  },
  attends: [],

// 각각의 state를 관리하는 주요 함수들을 정의합니다.
  setAttend: (newAttend) => set((state) => ({
    attend: {
      ...state.attend,
      ...newAttend,
    }
  })),
  setUpdateAttends: (newAttend) => set((state) => {
    const filteredAttends = state.attends.filter(
      (attend) =>
        attend.userId !== newAttend.userId ||
        attend.sessionId !== newAttend.sessionId ||
        attend.attendIdx !== newAttend.attendIdx
    );
    return { attends: [...filteredAttends, newAttend] };
  }),

}));

export default useAttendStore;
💡 Model은 다른 라이브러리에 의존하지 않아야 합니다. zustand 대신 TypeScript 등을 사용하여 정의하는 것이 좋습니다.

V(View)

보이는 화면을 정의합니다. component 폴더에서 view를 담당하고 있습니다.

// View 예시 코드 (AttendUpdateList)

const AttendUpdateList = () => {
  const location = useLocation();
  const { userId } = location.state || {};
  // viewModel 부분 불러오기
  const { attendanceRecords, loading, error, toggleAttend } = useAttendUpdateList(userId);
// 보이는 것과 관련된 함수 선언
// e.g. 출석 횟수가 3회일때는 초록색으로 보이도록 처리
  const calculateStatus = (attendList) => {
    if (!attendList || attendList.length === 0) return COLORS.light_gray;
    const checkedCount = attendList.filter((item) => item.status).length;
    if (checkedCount === 3) return COLORS.green;
    if (checkedCount >= 1) return COLORS.orange;
    return COLORS.red;
  };

// 보이는 부분 Return
  if (loading) return <Container>Loading...</Container>;
  if (error) return <Container>Error: {error}</Container>;

  return (
    <AttendanceContainer>
      {attendanceRecords.map((record, record_index) => {
        if (!record.session_date) {
          return null;
        }

        const { month, date, day } = getLocal(record.session_date);
        const finalStatus = calculateStatus(record.attendList);
        const attendListLength = record.attendList ? record.attendList.length : 0;
        const grayCirclesNeeded = 3 - attendListLength;

        return (
          <SessionContainer key={record_index}>
            <SessionName>{record.session_name}</SessionName>
            <RowContainer>
              <OnAirCircle color={finalStatus} />
              <DateContainer>
                {month}/{date}
                <br />
                {day}
              </DateContainer>
              {record.attendList && record.attendList.length > 0
                ? record.attendList.map((attend, attend_index) => (
                    <OnAirCircle
                      key={attend_index}
                      color={attend.status ? COLORS.green : COLORS.red}
                      onPress={() => toggleAttend(record_index, attend_index)}
                    />
                  ))
                : null}
              {[...Array(grayCirclesNeeded)].map((_, i) => (
                <OnAirCircle
                  key={attendListLength + i}
                  color={COLORS.light_gray}
                />
              ))}
            </RowContainer>
          </SessionContainer>
        );
      })}
    </AttendanceContainer>
  );
};

export default AttendUpdateList;

VM(ViewModel)

보이는 화면과 모델 사이의 로직을 관리합니다. Custom Hook형식으로 관리하고 있습니다.

// viewModel 예시 코드 (adminHook)

const useAttendUpdateList = (userId) => {
// 필요한 state 불러오기
  const { attendanceRecords, loading, error } = useAttendUpdate(userId);
  const { setUpdateAttends } = useAttendStore();
  const { updateData } = useListDataStore();

// 로직 처리에 필요한 함수 선언
// e.g. 현재 출석 현황을 toggle 형식으로 변경하여 저장하는 함수
  const toggleAttend = (record_index, attend_index) => {
    const target_record = attendanceRecords[record_index];
    const target_attend = target_record.attendList[attend_index];

    const new_attend = {
      userId: userId,
      sessionId: target_record.session,
      attendIdx: target_attend.attendIdx,
      status: !target_attend.status,
    };

    setUpdateAttends(new_attend);
    updateData((prev) => {
      const newRecords = [...prev];
      newRecords[record_index].attendList[attend_index].status =
        !newRecords[record_index].attendList[attend_index].status;
      return newRecords;
    });
  };
  return { attendanceRecords, loading, error, toggleAttend };
};