programming language/react

[React] 리덕스 코드 스플리팅(Redux Code Splitting) 사용하기

공대키메라 2022. 6. 26. 00:08

지난 시간에 Redux의 간단 사용법을 공식 사이트에서 소개하는 대로 사용해보았고(여기 클릭!), 개념에 대해서 알아보았다.(여기 클릭)

 

이번 시간에는 Redux에 대해 부족했던 설명으로 Code Splitting 하는 방법, 이를 합치는 Combine Reducer를 사용하는 방법과 함께 Redux-thunk, Redux Saga에 대해서 알아볼 것이다. 

 

이 내용에서 정리할 내용은 다음 사이트를 참고했다. 

 

참고:

https://redux.js.org/usage/structuring-reducers/splitting-reducer-logic

https://javascript.plainenglish.io/simple-application-of-redux-combinereducers-in-react-6ac3bbeabc4a

https://www.pluralsight.com/guides/code-splitting-your-redux-application

1. 리덕스 코드 스플리팅(Redux Code Splitting)

지난 시간에 Redux가 필요한 이유에 대해 간단하게 알아보았다. 

 

많은 State를 한번에 관리하기 위해서 Redux를 사용하는데 여기서 문제가 생길 수 있다.

 

Redux사용시에 reducer로 현재 상태와 액션 객체를 받아서 원하는 작업을 수행한다. 

 

하지만 이 reducer가 너무 비대해질 경우 문제가 생긴다. 

Motivation(동기)

모든 의미있는 어플리케이션에서, 하나의 reducer 함수에 모든 update 로직을 넣는것은 유지보수성을 떨어뜨린다.

물론 reducer를 사용함에 하나의 방식이 있는것만은 아니지만, reducer 함수들이 상대적으로 짧고 특정 기능 하나를 수생하는게 이상적이란것에 일반적으로 동의한다.

이것 떄문에, 길고 많은 일을 하는 코드를 이해하기 쉽도록 작게 쪼개는 연습을 하는게 좋은 프로그래밍이다.

Redux Reducer가 단지 함수이기 때문에, 같은 개념이 적용된다.
reducer 로직을 다른 함수로 쪼개고 부모 함수에서 그 새 함수를 부를 수 있다. 

 

Redux Code Splitting은 위의 설명처럼 한 reducer함수에 기능이 너무 비대해짐에 따라서 유지보수가 힘든 것을 보완하기 위한 방법으로 말할 수 있다. 

 

또한, 이를 위해 여러 용어들을 나누어서 설명하고 있다.

 

- 리듀서(reducer) : (statem action) -> newState 의 signature를 가진 어떤 함수

- 루트 리듀서(rootReducer) : createStore에서 처음 augument로 실제로 넘어온 reducer 함수

- 슬라이스 리듀서(slice reducer) : state tree의 특정 slice를 update하기 위해 사용되는 reducer. 주로 combine reducer를 넘겨서 사용한다. 

- 경우 함수 or 케이스 함수 (fase function) : 특정 action에서 update 로직을 다루기 위해 사용되는 함수. 이게 reducer 함수가 실제로 될지 모른다. 혹은 적절히 작동하도록 할 다른 파라미터가 필요할지 모른다. 

- 하이오더 리듀서 (High order Reducer) : 한 argument로 reducer 함수를 가지는 함수. 그리고, 혹은 결과로 combine reducer나 redux-undo같은 새로운 reducer 함수를 반환한다. 

 

우리는 그냥 reducer만 잇는 줄 알았는데 이 reducer를 세부적으로 역할에 따라 나누니 여러 용어로 분리할 수 있다.

 

그러면 실제로 코드 스플리팅을 적용해 보도록 하겠다. 

2. 리덕스 코드 스플리팅 적용하기 (Apply Redux Code Splitting) - combine reducer

위의 예시를 위해서 필자가 만들 화면이다. 

 

react boilder plate 로 프로젝트를 생성한 후, 

 

필자는 다음 명령어를 수행했다.

 

npm i antd 
npm i react-redux redux

 

프로젝트 구성은 다음과 같다.

 

src/app 폴더와 reducers폴더, 그리고 src내에 Counter.js, Text.js가 새롭게 만들어질 친구들이다. 

(당장 나랑 절교하자...)

index.js

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import store from "./app/store";
import { Provider } from "react-redux";

// As of React 18
const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

App.js

import "./App.css";
import Counter from "./Counter";
import Add100 from "./Add100";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Counter />
        <Add100 />
      </header>
    </div>
  );
}

export default App;

reducers.index.js

import { combineReducers } from "redux";
import number from "./number";
import add100 from "./add100";
const rootReducer = combineReducers({
  add100,
  number,
});

export default rootReducer;

 

rootReducer 안으로 number와 text reducer를 합치고 있다. 

Counter.js

import { Button } from "antd";
import "antd/dist/antd.css";
import { useDispatch, useSelector } from "react-redux";
import {
  addNumberAction,
  divNumberAction,
  mulNumberAction,
  selectNumber,
  subNumberAction,
} from "./reducers/number";

const Counter = () => {
  const number = useSelector(selectNumber);
  const dispatch = useDispatch();

  return (
    <>
      <div style={{ color: "white" }}>{number}</div>
      <div>
        <Button type="primary" onClick={() => dispatch(addNumberAction(1))}>
          add number
        </Button>
        <Button
          type="danger"
          style={{ marginLeft: 20, marginRight: 20 }}
          onClick={() => dispatch(subNumberAction(1))}
        >
          substract number
        </Button>
        <Button
          type="primary"
          style={{ marginRight: 20 }}
          onClick={() => dispatch(mulNumberAction(2))}
        >
          multiply number
        </Button>
        <Button type="danger" onClick={() => dispatch(divNumberAction(2))}>
          divide number
        </Button>
      </div>
    </>
  );
};

export default Counter;


총 4개의 버튼을 만들었다.

 

덧셈, 뺄셈, 곱셈, 나눗셈 기능인데 dispatch를 각각에 맞게 배치해서 state를 변환하고 있다. 

 

import한 action들을 자세히 보려면 다음을 확인하면 된다.

reducers/number.js

const initialState = {
  number: 0,
};

const ADD_NUMBER = "ADD_NUMBER";
const SUB_NUMBER = "SUB_NUMBER";
const MUL_NUMBER = "MUL_NUMBER";
const DIV_NUMBER = "DIV_NUMBER";

export const addNumberAction = (data) => {
  return {
    type: ADD_NUMBER,
    data,
  };
};

export const subNumberAction = (data) => {
  return {
    type: SUB_NUMBER,
    data,
  };
};

export const mulNumberAction = (data) => {
  return {
    type: MUL_NUMBER,
    data,
  };
};

export const divNumberAction = (data) => {
  return {
    type: DIV_NUMBER,
    data,
  };
};

export const selectNumber = (state) => {
  return state.rootReducer.number.number;
};

const reducer = (state = initialState, action) => {
  console.log("action.type : ", action.type);
  switch (action.type) {
    case ADD_NUMBER:
      return {
        ...state,
        number: state.number + action.data,
      };
    case SUB_NUMBER:
      return {
        ...state,
        number: state.number - action.data,
      };
    case MUL_NUMBER:
      return {
        ...state,
        number: state.number * action.data,
      };
    case DIV_NUMBER:
      return {
        ...state,
        number: state.number / action.data,
      };
    default:
      return state;
  }
};

export default reducer;

reducers/add100.js

const initialState = {
  add100: 0,
};

const ADD_100 = "ADD_100";

export const add100Action = () => {
  return {
    type: ADD_100,
  };
};

export const selectAdd100 = (state) => {
  return state.add100.add100;
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_100:
      return {
        ...state,
        add100: state.add100 + 100,
      };
    default:
      return state;
  }
};

export default reducer;

 

여기서 간단하게 테스트를 해보고 가자. (갑자기?)

 

 

자! 다음을 보면 생각보다 의아한 결과를 확인할 수 있다. 

같은 빈 객체인데 비교했는데 다르네?

 

그렇다... 중괄호로 객체를 만들어 낼 때 매다 무조건 새로운 객체가 생성되는 것이다. 

 

그러면 같은 값을 가지고 있는 객체를 만들려면 어떻게 하면 쉬울까?

 

 

 

다음과 같이 할 수 있다. 이 spread 연산자를 이용해서 기존의 데이터를 그대로 가져오고, 원하는 값을 수정하면 정말로 우리가 원하는 새로운 객체가 생성되는 것이다. 

 

이렇게 하는 이유는 reducer의 불변성(immortality)을 유지하기 위해서다. 

 

그래서 실행 결과는 다음과 같다. 

 


이렇게 기존의 reducers들을 잘게 잘라서 관리가 된다면 유지보수성이 향상할것으로 기대된다.

 

다음 시간에는 Redux thunk, Redux saga에 대해서 알아보려고 한다.