[달력 만들기 toy project] 상태 관리 ③ Redux toolkit : month, year 변경 기능

이민선(Jasmine)·2023년 5월 2일
2
post-thumbnail

오늘은 지난 시간에 Redux로 구현했던 연도, 월 변경 기능을 redux toolkit을 사용하여 리팩토링해보려고 한다.

일단 설치부터 고고싱!!

npm install @reduxjs/toolkit

action → createAction

세상에 마상에.. 미쳤다 미쳤어.. 코드가 80%나 줄었다 ㅋㅋㅋㅋㅋㅋ

actions.tsx

// createAction으로 대체 ✨
import { createAction } from "@reduxjs/toolkit";

export const increaseYear = createAction("INCREASEYEAR");
export const decreaseYear = createAction("DECREASEYEAR");
export const increaseMonth = createAction("INCREASEMONTH");
export const decreaseMonth = createAction("DECREASEMONTH");

// -------------------------------------------------------- 
// 기존 코드 바이바이~~ 👋
// export const INCREASEYEAR = "INCREASEYEAR";
// export const DECREASEYEAR = "DECREASEYEAR";
// export const INCREASEMONTH = "INCREASEMONTH";
// export const DECREASEMONTH = "DECREASEMONTH";

// export const increaseYear = () => {
//   return {
//     type: INCREASEYEAR,
//   };
// };
// export const decreaseYear = () => {
//   return {
//     type: DECREASEYEAR,
//   };
// };
// export const increaseMonth = () => {
//   return {
//     type: INCREASEMONTH,
//   };
// };
// export const decreaseMonth = () => {
//   return {
//     type: DECREASEMONTH,
//   };
// };

와우 toolkit 증말 신세계네 ??!!

createAction에 action 이름만 담으면 저렇게 짧은 코드로 알아서 action을 생성해준다. 보일러 플레이트 코드라는 Redux의 단점이 toolkit으로 완화될 수 있다는 게 초장부터 확 체감되잖아??!!

다음으로 reducer도 createReducer로 변경시켜보자.

reducer → createReducer

reducer.tsx

import { createReducer } from "@reduxjs/toolkit";
.
.
.
(중략)
// createReducer로 대체 ✨
export const reducer = createReducer(
  { year: date.getFullYear(), month: date.getMonth() },
  (builder) => {
    builder
      .addCase(increaseYear, (state) => {
        return { year: state.year + 1, month: state.month };
      })
      .addCase(decreaseYear, (state) => {
        return { year: state.year - 1, month: state.month };
      })
      .addCase(increaseMonth, (state) => {
        return state.month === 11
          ? { year: state.year + 1, month: 0 }
          : { year: state.year, month: state.month + 1 };
      })
      .addCase(decreaseMonth, (state) => {
        return state.month === 0
          ? { year: state.year - 1, month: 11 }
          : { year: state.year, month: state.month - 1 };
      });
  }
);

// -------------------------------------------------------- 
// 기존 코드 바이바이~~ 👋
// export const reducer = (
//   state: Props = { year: date.getFullYear(), month: date.getMonth() },
//   action: Action
// ) => {
//   switch (action.type) {
//     case INCREASEYEAR:
//       return { year: state.year + 1, month: state.month };
//     case DECREASEYEAR:
//       return { year: state.year - 1, month: state.month };
//     case INCREASEMONTH:
//       return state.month === 11
//         ? { year: state.year + 1, month: 0 }
//         : { year: state.year, month: state.month + 1 };
//     case DECREASEMONTH:
//       return state.month === 0
//         ? { year: state.year - 1, month: 11 }
//         : { year: state.year, month: state.month - 1 };
//     default:
//       return state;
//   }
// };

기존에는 reducer의 default parameter로 초기값을 지정해줬었는데, createReducer를 사용하니 그냥 바로 초기값을 인자로 전달하면 된다.

그리고 builder callback이라는 것을 사용해서 switch문을 대체할 수 있다. 각각의 addCase 메서드에서 첫번째 인자로는 action의 type을, 두번째 인자로는 return할 갱신된 state을 callback 함수 형태로 넣으면 된다.

switch 문일 때와 달리 체인 방식으로 메서드를 호출한다. 음.. switch 문에 비해 builder callback이 가독성과 유지보수성 측면에서 낫다고 평가된다고 하지만, 그래도 나한테만큼은 createReducer가 아직까지는 막 획기적이라고 와닿는 정도는 아니다. createAction을 처음 봤을 때 코드의 80%가 증발하는 충격이 워낙 컸기 때문이기도 할 것이다 ㅋㅋㅋㅋㅋ

하지만 아직 긴장을 놓긴 이르다. 더 충격적으로 코드의 양을 줄여주는 createSlice 메서드도 있다. 한번 활용해서 코드 양을 더더욱 줄여보자.

actions, reducer → createSlice

store.tsx

import { legacy_createStore as createStore } from "redux";
import { createSlice } from "@reduxjs/toolkit";

const date = new Date();

interface Props {
  year: number;
  month: number;
}

// createSlice 메서드 내부에 초기 상태, reducer, action이 몽땅 다 들어있다!!!
export const calendar = createSlice({
  name: "calendarReducer",
  initialState: { year: date.getFullYear(), month: date.getMonth() },
  reducers: {
    increaseYear: (state: Props) => {
      return { year: state.year + 1, month: state.month };
    },
    decreaseYear: (state: Props) => {
      return { year: state.year - 1, month: state.month };
    },
    increaseMonth: (state: Props) => {
      return state.month === 11
        ? { year: state.year + 1, month: 0 }
        : { year: state.year, month: state.month + 1 };
    },
    decreaseMonth: (state: Props) => {
      return state.month === 0
        ? { year: state.year - 1, month: 11 }
        : { year: state.year, month: state.month - 1 };
    },
  },
});

export const store = createStore(calendar.reducer);

// actions 속성을 직접 정의한 적이 없는데, 자동으로 calendar의 속성이 생겨난다.
export const { increaseYear, decreaseYear, increaseMonth, decreaseMonth } =
  calendar.actions;

진짜 실화??? createSlice 내부에 객체를 인자로 전달하는데,
이 객체에는 3개의 속성이 들어있다.

  • name: 생성된 action type의 prefix
  • state의 초기 값
  • reducer(key: 메서드 이름 / value: case reducer 함수)

이렇게 몽땅 다들어 있다. 기존의 코드를 압축 또 압축해서 아주 짧게 만들 수 있다. 왜 toolkit이 강려-크하게 권장되는지 아주 잘 알 것 같다.

일단 action을 별도로 정의하지 않고, createSlice에 인자로 전달하는 객체 안에서 case reducer 함수를 정의하기만 하면 자동으로 calendar 객체의 속성이 된다는 게 가장 편하다. 마지막 줄에 구조분해 할당해서 actions를 export 하면 된다.

mutate state 허용...?

심지어 더욱 놀라운 사실! createSlice를 쓰면 mutate state가 허용된다. redux 사용하기 시작할 때 state의 불변성을 유지하는 것이 아주 중요하다고 배웠지만, createSlice를 사용하면 state 불변성에 크게 신경 쓰지 않고 코드를 작성할 수 있다. 아래의 공식문서 예시와 같이 action.payload를 state에 push도 할 수 있다.

// Redux toolkit 공식 문서 일부 발췌
const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    todoAdded(state, action) {
      // ✅ This "mutating" code is okay inside of createSlice!
      state.entities.push(action.payload)
    },

출처: redux toolkit 공식 문서
https://ko.redux.js.org/tutorials/fundamentals/part-8-modern-redux/#using-createslice

아니 이게 어떻게 가능하다는 말임??!
우리가 mutate state 하더라도, Redux toolkit은 immer 라이브러리를 사용하여 작성된 produce 함수를 사용하여 내부에서 자동으로 상태 불변성을 유지해준다. 쏘 어메이징,,

앞으로도 전역 상태 관리는 redux toolkit을 많이 사용하게 될 것 같다! 아직 못 배운 메서드들도 많은데, 다른 곳에서 써보면서 또 필요하면 계속 익혀봐야겠다.

다음 시간에는 styled-component로 이번 달 날짜가 아니면 흐린 색으로 나오도록 해보자!

profile
기록에 진심인 개발자 🌿

0개의 댓글