구글 캘린더 클론코딩(5) - 상태관리 Redux

Kay·2023년 6월 29일
0

"react": "^18.2.0",
"typescript": "^4.9.5",
"react-redux": "^8.1.1",
"@reduxjs/toolkit": "^1.9.5",

Redux toolkit 공부 정리 - [SEB_FE_45] 2023.06.21 / Redux & Reduxjs/toolkit & React-redux

Redux toolkit을 학습하고 프로젝트에 적용해보았다.

개념을 이해하고 적용하는 것까지는 무리없이 마무리하였지만,
다음 부분에서 고민이 되었다.

  1. state를 어디서 관리할지
  2. state의 변경으로 url이 변경
  3. url의 변경으로 state가 변경

이에 대한 고민을 정리한 내용을 담아보려고 한다.

고민의 시작

우선 화면을 보면 크게 mini 달력과 main 달력과 GNB 영역으로 나눌 수 있다.

  • Main(다음 컴포넌트를 포함하고 있는 상위 컴포넌트)
  • mini 달력(MiniCalendar 컴포넌트)
  • main 달력(MainCalendar 컴포넌트)
  • GNB(GNB 컴포넌트)

그리고 전역적으로 저장해야할 state는 크게 drilldownView와 viewDate로 나뉘었다.

drilldownView가 영향을 미치는 곳

  • "월, 주, 일"이 옵션인 select에서 선택 -> url에 첫번째 params에 해당하는 부분 변경
  • url에 첫번째 params에 해당하는 부분 변경 -> select 옵션 변경
  • 즉 서로가 영향을 주는 영역이다.

viewDate가 영향을 미치는 곳

  • 영향을 주는 요소를 주황색으로, 영향을 받는 요소를 빨간색으로 표시해보았다.
  • 여기서도 마찬가지로 url의 "년/월/일"에 해당하는 부분이 바뀌면 보여주는 달력 화면에 영향을 미치고, 달력에서 날짜 이동이 있으면 url의 "년/월/일"이 변해야 한다.

각각의 상태와 url 관리 어디서하지?

처음에는 깊이 생각하지 못하고 이곳 저곳에서 action 함수를 호출하고 react-router-dom의 navigate 함수를 호출하였다.

그러다보니 불필요한 action 함수 호출과 url이 여러번 변하는 등의 문제가 발생하였다.

코드를 정리한채 나름의 규칙을 세웠다.

1. url을 navigate하는 함수는 상위 컴포넌트에서 한다.

url과 state(viewDate와 drilldownView)는 서로서로 영향을 줘야하는 상황이기 때문에
navigate 코드가 이곳저곳에 있을 경우 코드를 읽기도 어렵고 어떤 반응이 일어날지 예상하기도 힘들었다.

따라서 이 둘이 서로 영향을 주는 코드는 상위 컴포넌트에 정리해야겠다는 생각이 들었다.

케이스를 둘로 나누었다.
1. Case 1. url이 수정되는 경우
2. Case 2. state를 수정하는 경우

두번째 케이스는 생각하기 쉬웠다.
이미 상태가 바뀌었으니 url만 수정해주면 된다!

    useEffect(() => {
        // Case 2. state를 수정하는 경우
        navigate(`${drilldownView}/${viewDate.year}/${viewDate.month}/${viewDate.date}`);
    }, [drilldownView, viewDate.year, viewDate.month, viewDate.date])

하지만 첫번째 케이스는 생각해야할 것들이 있었다.
1 - 1. 사용자가 url 을 정상적으로 입력하지 않은 경우 ex) mon/2023//

  • 이때는 입력받은 값을 무시하고 오늘의 날짜로 state(viewDate와 drilldownView)과 url을 변경하도록 했다.
if (!params.drilldownView || !params.year || !params.month || !params.date) {
   const today = moment().format();
   const temp = getViewDateObj(today);

   dispatch(changeDrilldownView('month'));
   dispatch(changeViewDate(temp));
   return;
}

1 - 2. 기존 url과 동일한 url을 입력한 경우

  • 이때는 그냥 무시하도록 return 처리를 해주었다.

1 - 3. url에서 drilldownView 부분이 다른 경우

  • state(drilldownView)를 수정하도록 하였다.
if (drilldownView !== params.drilldownView 
    && (params.drilldownView === 'month' 
    || params.drilldownView === 'week'
    || params.drilldownView === 'day')
) {
     dispatch(changeDrilldownView(drilldownView));
}

1 - 4. url에서 날짜부분(pathDate)이 다른 경우

  • state(viewDate)를 수정하도록 하였다.
if (isDiffBetweenViewDateAndPathDate(viewDate, pathDate)) {
    dispatch(changeViewDate({ year: pathDate.year, month: pathDate.month, date: pathDate.date }));
}

Main 컴포넌트의 useEffect 전체 코드

    const { viewDate, drilldownView } = useAppSelector((state) => state.main);
    const dispatch = useAppDispatch();

    const navigate = useNavigate();
    const params = useParams();
    const pathDate = { year: params.year || '', month: params.month || '', date: params.date || '' };

    useEffect(() => {
        // Case 1. url이 수정되는 경우
        // Case 1 - 1. url 을 정상적으로 입력하지 않은 경우 -> 오늘 날짜로 수정 및 이동
        if (!params.drilldownView || !params.year || !params.month || !params.date) {
            const today = moment().format();
            const temp = getViewDateObj(today);

            dispatch(changeDrilldownView('month'));
            dispatch(changeViewDate(temp));
            return;
        }

        // Case 1 - 2. url이 기존과 동일할 경우 -> 수정 필요 없음
        if (drilldownView === params.drilldownView && !isDiffBetweenViewDateAndPathDate(viewDate, pathDate)) {
            return;
        }

        // Case 1 - 3. url에서 drilldownView 부분이 다른 경우 -> state 수정
        if (drilldownView !== params.drilldownView 
            && (params.drilldownView === 'month' 
                || params.drilldownView === 'week'
                || params.drilldownView === 'day')) {
            dispatch(changeDrilldownView(drilldownView));
        }

        // Case 1 - 4. url에서 날짜 부분이 다른 경우 -> state 수정
        if (isDiffBetweenViewDateAndPathDate(viewDate, pathDate)) {
            dispatch(changeViewDate({ year: pathDate.year, month: pathDate.month, date: pathDate.date }));
        }

    }, [drilldownView, params.year, params.month, params.pathDate])

    useEffect(() => {
        // Case 2. state를 수정하는 경우
        navigate(`${drilldownView}/${viewDate.year}/${viewDate.month}/${viewDate.date}`);
    }, [drilldownView, viewDate.year, viewDate.month, viewDate.date])

마무리

정리해보면 너무나 당연한 이야기이지만 정작 state를 다루려고 하는 상황이 오면 꼭 헤매는 것 같다.
프로젝트들을 통해 state를 관리하는 경험을 많이 쌓아 직관적으로 state를 관리할 곳을 바로 찾을 수 있도록 해야겠다.

0개의 댓글