관리 메뉴

거니의 velog

(7) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 2 본문

React/React_리액트 심화

(7) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 2

Unlimited00 2023. 12. 13. 16:27

3. 리덕스 관련 코드 작성하기

* 이제 프로젝트에 리덕스를 사용해 보자. 리덕스 관련 코드를 준비하자. 리덕스를 사용할 때는 액션 타입, 액션 생성 함수, 리듀서 코드를 작성해야 한다. 이 코드들을 각각 다른 파일에 작성하는 방법도 있고, 기능별로 묶어서 파일 하나에 작성하는 방법도 있다.

일반적인 구조

* 위 그림은 가장 일반적인 구조로 actions, constrains, reducers 라는 세 개의 디렉터리를 만들고 그 안에 기능별로 파일을 하나씩 만드는 방식이다. 코드를 종류에 따라 다른 파일에 작성하여 정리할 수 있어서 편리하지만, 새로운 액션을 만들 때마다 세 종류의 파일을 모두 수정해야 하기 때문에 불편하기도 하다. 이 방식은 리덕스 공식 문서에서도 사용되므로 가장 기본적이라 할 수 있지만, 사람에 따라서는 불편할 수도 있는 구조이다.

Ducks 패턴

* 위 그림은 액션 타입, 액션 생성 함수, 리듀서 함수를 기능 별로 파일 하나에 몰아서 다 작성하는 방식이다. 이러한 방식을 Ducks 패턴이라고 부르며, 앞서 설명한 일반적인 구조로 리덕스를 사용하다가 불편함을 느낀 개발자들이 자주 사용한다.

* 리덕스 관련 코드에 대한 디렉터리 구조는 정해진 방법이 없기 때문에 마음대로 작성해도 되지만, 위 두 가지 방법이 주로 사용된다. 우리는 두 번째로 소개한 방식인 Ducks 패턴을 사용하여 코드를 작성해 보겠다.


(1) counter 모듈 작성하기

* Ducks 패턴을 사용하여 액션 타입, 액션 생성 함수, 리듀서를 작성한 코드를 '모듈'이라고 한다. 먼저 counter 모듈을 작성해 보자.

[1] 액션 타입 정의하기

* modules 디렉터리를 생성하고 그 안에 counter.js 파일을 다음과 같이 작성하자.

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

* 가장 먼저 해야 할 작업은 액션 타입을 정의하는 것이다. 액션 타입은 대문자로 정의하고, 문자열 내용은 '모듈 이름/액션 이름'과 같은 형태로 작성한다. 문자열 안에 모듈 이름을 넣음으로써, 나중에 프로젝트가 커졌을 때 액션의 이름이 충돌되지 않게 해 준다. 예를 들어 SHOW 혹은 INITIALIZE라는 이름을 가진 액션은 쉽게 중복될 수 있다. 하지만 앞에 모듈 이름을 붙여 주면 액션 이름이 겹치는 것을 걱정하지 않아도 된다.

[2] 액션 생성 함수 만들기

* 액션 타입을 정의한 다음에는 액션 생성 함수를 만들어 주어야 한다.

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

* 더 필요하거나 추가할 값이 없으니 그냥 위와 같이 만들어 주면 된다. 꽤 간단하다. 여기서 주의해야 할 점은 앞부분에 export라는 키워드가 들어간다는 것이다. 이렇게 함으로써 추후 이 함수를 다른 파일에서 불러와 사용할 수 있다.

[3] 초기 상태 및 리듀서 함수 만들기

* 이제 counter 모듈의 초기 상태와 리듀서 함수를 만들어 주자.

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

const initialState = {
  number: 0,
};

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1,
      };
    case DECREASE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}

export default counter;

* 이 모듈의 초기 상태에는 number 값을 설정해 주었으며, 리듀서 함수에는 현재 상태를 참조하여 새로운 객체를 생성해서 반환하는 코드를 작성해 주었다. 마지막으로 export default로 내보내 주었는데, 두 방식의 차이점은 export는 여러 개를 내보낼 수 있지만 export default는 단 한 개만 내보낼 수 있다는 것이다.

* 불러오는 방식도 다르다. 다음과 같이 말이다.

import counter from './counter';
import { increase, decrease } from './counter';
// 한꺼번에 불러오고 싶을 때
import counter, { increase, decrease } from './counter';

(2) todos 모듈 만들기

* 이번에 만들 모듈은 좀 더 복잡하다. modules 디렉터리에 todos.js 파일을 생성하자.

[1] 액션 타입 정의하기

* 이전과 마찬가지로 가장 먼저 해야 할 일은 액션 타입 정의이다.

const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 인풋 값을 변경함
const INSERT = 'todos/INSERT'; // 새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; // todo를 체크/체크 해제함
const REMOVE = 'todos/REMOVE'; // todo를 제거함

[2] 액션 생성 함수 만들기

* 다음으로 액션 생성 함수를 만든다. 조금 전과 달리 이번에는 액션 생성 함수에서 파라미터가 필요하다. 전달받은 파라미터는 액션 객체 안에 추가 필드로 들어가게 된다.

const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 인풋 값을 변경함
const INSERT = 'todos/INSERT'; // 새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; // todo를 체크/체크 해제함
const REMOVE = 'todos/REMOVE'; // todo를 제거함

export const changeInput = (input) => ({
  type: CHANGE_INPUT,
  input,
});

let id = 3; // insert가 호출될 때마다 1씩 더해진다.
export const insert = (text) => ({
  type: INSERT,
  todo: {
    id: id++,
    text,
    done: false,
  },
});

export const toggle = (id) => ({
  type: TOGGLE,
  id,
});

export const remove = (id) => ({
  type: REMOVE,
  id,
});

* 위 액션 생성 함수 중에서 insert 함수는 액션 객체를 만들 때 파라미터 외에 사전에 이미 선언되어 있는 id라는 값에도 의존한다. 이 액션 생성 함수는 호출될 때마다 id 값에 1 씩 더해 준다. 이 id 값은 각 todo 객체가 들고 있게 될 고윳값이다.

* 여기서 id 값이 3인 이유는 다음 절에서 초기 상태를 작성할 때 todo 객체 두 개를 사전에 미리 넣어 둘 것이므로 그 다음에 새로 추가될 항목의 id가 3이기 때문이다.

[3] 초기 상태 및 리듀서 함수 만들기

* 이제 모듈의 초기 상태와 리듀서 함수를 작성한다. 이번에는 업데이트 방식이 조금 까다로워진다. 객체에 한 개 이상의 값이 들어가므로 불변성을 유지해 주어야 하기 때문이다. spread 연산자(...)를 잘 활용하여 작성해 보자. 배열에 변화를 줄 때는 배열 내장 함수를 사용하여 구현하면 된다.

const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 인풋 값을 변경함
const INSERT = 'todos/INSERT'; // 새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; // todo를 체크/체크 해제함
const REMOVE = 'todos/REMOVE'; // todo를 제거함

export const changeInput = (input) => ({
  type: CHANGE_INPUT,
  input,
});

let id = 3; // insert가 호출될 때마다 1씩 더해진다.
export const insert = (text) => ({
  type: INSERT,
  todo: {
    id: id++,
    text,
    done: false,
  },
});

export const toggle = (id) => ({
  type: TOGGLE,
  id,
});

export const remove = (id) => ({
  type: REMOVE,
  id,
});

const initialState = {
  input: '',
  todos: [
    {
      id: 1,
      text: '리덕스 기초 배우기',
      done: true,
    },
    {
      id: 2,
      text: '리액트와 리덕스 사용하기',
      done: false,
    },
  ],
};

function todos(state = initialState, action) {
  switch (action.type) {
    case CHANGE_INPUT:
      return {
        ...state,
        input: action.input,
      };
    case INSERT:
      return {
        ...state,
        todos: state.todos.concat(action.todo),
      };
    case TOGGLE:
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.id ? { ...todo, done: !todo.done } : todo,
        ),
      };
    case REMOVE:
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.id),
      };
    default:
      return state;
  }
}

export default todos;

(3) 루트 리듀서 만들기

* 이번 프로젝트에서는 리듀서를 여러 개 만들었다. 나중에 createStore 함수를 사용하여 스토어를 만들 때는 리듀서를 하나만 사용해야 한다. 그렇기 때문에 기존에 만들었던 리듀서를 하나로 합쳐 주어야 하는데, 이 작업은 리덕스에서 제공하는 combineReducers 라는 유틸 함수를 사용하면 쉽게 처리할 수 있다.

* modules 디렉터리에 index.js 파일을 만들고, 그 안에 다음과 같은 코드를 작성하자.

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;

* 파일 이름을 이렇게 index.js로 설정해 주면 나중에 불러올 때 디렉터리 이름까지만 입력하여 불러올 수 있다. 다음과 같이 말이다.

import rootReducer from './modules';

4. 리액트 애플리케이션에 리덕스 적용하기

* 이제 드디어 리액트 애플리케이션에 리덕스를 적용할 차례이다. 스토어를 만들고 리액트 애플리케이션에 리덕스를 적용하는 작업은 src 디렉터리의 index.js에서 이루어진다.


(1) 스토어 만들기

* 가장 먼저 스토어를 생성한다.

[src/index.js]

import React from 'react';
import ReactDOM from 'react-dom/client';
import { createStore } from 'redux';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import rootReducer from './modules';

const store = createStore(rootReducer);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

reportWebVitals();

(2)  Provider 컴포넌트를 사용하여 프로젝트에 리덕스 적용하기

* 리액트 컴포넌트에서 스토어를 사용할 수 있도록 App 컴포넌트를 react-redux에서 제공하는 Provider 컴포넌트로 감싸 준다. 이 컴포넌트를 사용할 때는 store를 props로 전달해 주어야 한다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import rootReducer from './modules';

const store = createStore(rootReducer);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>,
);

reportWebVitals();

(3) Redux DevTolls의 설치 및 적용

* Redux RevTools는 리덕스 개발자 도구이며, 크롬 확장 프로그램으로 설치하여 사용할 수 있다. 크롬 웹 스토어에서 Redux DevTools를 검색하여 설치해 주자.

https://chromewebstore.google.com/search/Redux%20Devtools?hl=ko

 

Chrome 웹 스토어

디스커버확장 프로그램테마

chromewebstore.google.com

* 설치하고 나면 리덕스 스토어를 만드는 과정에서 다음과 같이 적용해 줄 수 있다.

// 사용 예시

const store = createStore(
	rootReducer, /* preloadedState, */
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

* 하지만 패키지를 설치하여 적용하면 코드가 훨씬 깔끔해진다. 우리는 패키지를 설치하는 형태로 적용해 보겠다(패키지를 설치하여 사용한다고 해도 크롬 확장 프로그램은 설치해야 한다).

* 우선 redux-devtools-extension을 yarn을 사용하여 설치해 주자.

$ yarn add redux-devtools-extension

* 그리고 다음과 같이 적용해 주면 된다.

[src/index.js]

import React from 'react';
import ReactDOM from 'react-dom/client';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import rootReducer from './modules';

const store = createStore(rootReducer, composeWithDevTools());

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>,
);

reportWebVitals();

* 이제 브라우저에서 크롬 개발자 도구를 실행한 후 Redux 탭을 열어 보자. 리덕스 개발자 도구가 잘 나타나는가?

Redux DevTools

* 리덕스 개발자 도구 안의 State 버튼을 눌러 현재 리덕스 스토어 내부의 상태가 잘 보이는지 확인해 보자.