본문 바로가기
웹 프로그래밍/👨‍👨‍👧‍👧소셜 가계부

[React/Node.js/Express/MongoDB] 소셜 가계부 프로젝트 구현 일지: 리덕스(Redux)로 가계부 데이터 상태 관리 1(리덕스 선택이유, 리덕스 정의 및 스토어, 액션, 리듀서 정의, 덕스패턴 적용까지)

by 청량리 물냉면 2024. 3. 7.
반응형

리덕스

  • 리액트 생태계에서 가장 사용률이 높은 상태관리 라이브러리
  • 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜 더욱 효율적으로 관리할 수 있게 도와준다.
  • 글로벌 상태 관리도 손쉽게 할 수 있다.
  • 미들웨어로 다양한 작업(비동기, 로깅) 가능

 

🤔 왜 상태관리 라이브러리로 리덕스를 선택했는가?

 

npmtrends를 통해 살펴보면, Redux는 다른 상태관리 라이브러리보다 월등히 높은 사용률을 보이고 있다.

요즘 채용공고를 보면 recoil, zustand도 제법 보이는데 recoil의 경우 1년 전이 마지막 update라 관리가 안 되고 있는 느낌이었고, zustand는 다른 프로젝트에서 한 번 다루어보려고 계획하고 있다.

Redux는 Context API가 지금 형태로 사용되기 전(`useReducer` 훅이 존재하기도 전)에 만들어진 라이브러리이다. 모든 상태관리 라이브러리의 근본이라는 느낌이라, 프로젝트에서 사용할 상태관리 라이브러리는 큰 고민 없이 Redux로 결정했다. 

 

 

리덕스 설치

npm i redux react-redux

 

 

Action 생성함수 생성하기

🐱‍🏍 액션Action

  • 앱에서 store로 보내는 데이터 묶음
  • state에 어떠한 변화가 필요할 때 발생시킴
  • 하나의 객체로 표현된다.
  • `type` 필드는 필수이며, 그 외의 값들은 개발자 재량
  • `store.dispatch()`를 사용해 액션을 보낼 수 있다.
{
  type: SET_VISIBILITY_FILTER,
  filter: SHOW_COMPLETED
}

 

🚖 액션 생성자 함수Action Creator

  • 액션을 만드는 함수.
  • 파라미터를 받아와 액션 객체 형태로 만들어준다.
  • 컴포넌트에서 쉽게 액션을 발생시키기 위해 사용한다.
// src/redux/transaction/action.js

// actionTypes 정의
export const ADD_TRANSACTION = "ADD_TRANSACTION";
export const EDIT_TRANSACTION = "EDIT_TRANSACTION";
export const REMOVE_TRANSACTION = "REMOVE_TRANSACTION";

// 액션 생성자 함수 정의
export const addTransaction = (transaction) => ({
  type: ADD_TRANSACTION,
  payload: transaction,
});

export const editTransaction = (transaction) => ({
  type: EDIT_TRANSACTION,
  payload: transaction,
});

export const removeTransaction = (id) => ({
  type: REMOVE_TRANSACTION,
  payload: id,
});

 

화살표 함수로 3개의 액션을 정의했다.

  • addTransaction: 가계부 내역 추가 함수
  • editTransaction: 가계부 내역 수정 함수
  • removeTransaction: 가계부 내역 삭제 함수

각각의 함수에서 사용하고 있는 payload는 상태를 변경시키는 데 필요한 데이터이다.

예를 들어, 값을 추가하기 위한 함수에는 당연히 추가할 가계부 데이터가 필요하다. 리듀서 함수에서 처리할 수 있도록  매개변수로 가져온 가계부 데이터를 payload로 지정해 주었다.

 

* payload

  • 객체 형태
  • 해당 액션에 필요한 추가 데이터
  • payload의 데이터는 리듀서에서 사용되어 상태를 업데이트하는 데 활용된다.

 

Reducer 정의하기

🤩 리듀서Reducer

  • 변화를 일으키는 함수
  • 현재 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수
  • 반드시 순수함수로 작성되어야 한다.
  • 여러 개의 리듀서(서브 리듀서)를 만들고 이를 합쳐 루트 리듀서를 만들 수 있다.
// src/redux/transaction/reducers.js

import { ADD_TRANSACTION, EDIT_TRANSACTION, REMOVE_TRANSACTION } from "./actions";

// 초기 상태 정의
const initialState = {
  transactions: [],
};

// 리듀서 함수 정의
const transactionReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TRANSACTION:
      return {
        ...state,
        transactions: [...state.transactions, action.payload],
      };
    case EDIT_TRANSACTION:
      return {
        ...state,
        transactions: state.transactions.map((transaction) =>
          transaction.id === action.payload.id ? action.payload : transaction,
        ),
      };
    case REMOVE_TRANSACTION:
      return {
        ...state,
        transactions: state.transactions.filter((transaction) => transaction.id !== action.payload),
      };
    default: //default는 기존 state를 그대로 반환하도록 해야 한다
      return state;
  }
};

export default transactionReducer;

전체적인 데이터 형식은 배열 안에 가계부 객체가 들어와 있는 형태이다. 따라서 초기값은 빈 배열로 정했다.

 

ADD_TRANSACTION은 기존 저장된 값에 payload로 받아온 새로운 가계부 데이터를 붙여 넣는 방식으로 동작한다.

EDIT_TRANSACTION은 기존 저장된 가계부 데이터를 순회하며 수정 요청이 들어온 가계부 데이터의 id와 동일한 id를 가진 데이터를 찾고, 해당 데이터의 내용을 새로운 가계부 데이터로 바꿔주는 원리로 구현했다.

REMOVE_TRANSACTION의 경우는 payload로 들어온 id값이 아닌 데이터만 필터링하는 식으로 삭제를 구현해 주었다.

 

 

스토어(Store) 만들기

💁‍♀️ 스토어(Store) 

  • 앱의 전체 상태 트리를 가지고 있는 저장소
  • 클래스가 아닌 메서드가 들어있는 객체이다.
  • 스토어 생성 방법은 루트 리듀싱 함수(루트 리듀서)를 `createStore`에 전달하는 것이다.
  • 스토어의 상태는 읽기 전용. 상태를 바꾸는 유일한 방법은 액션을 보내는 것뿐이다.
  • 스토어 안에는 현재의 앱 상태, 리듀서, 추가적으로 몇 가지 내장 함수들이 들어간다.
  • 리액트 프로젝트에서는 단 하나의 스토어만 존재한다.
// src/redux/transaction/store.js

import { createStore } from "redux";
import transactionReducer from "./reducers";

const store = createStore(transactionReducer);

export default store;

 

 

🦆 Ducks 패턴 적용

가계부 어플을 관리하기 위한 리듀서까지 작성하고 실제 데이터를 가져와 써보니(dispatch 부분은 다음 포스팅에...) 원하는 대로 잘 동작하고 있음을 확인할 수 있었다.

다만 리덕스 파일이 중구난방으로 흩어져 있는 게 보기 싫어 파일을 어떻게 관리하는 게 좋을까 고민이 되었다.

그러던 와중 Redux의 덕스Ducks 패턴을 발견했다.

 

GitHub - erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux

A proposal for bundling reducers, action types and actions when using Redux - erikras/ducks-modular-redux

github.com

 

 

[Redux] 리덕스 모듈 만들기 - 덕스 패턴(Ducks pattern)

리덕스 모듈 리덕스 모듈이란? 액션 타입(Actions), 액션 생성 함수(Action Creators), 리듀서(Reducer)가 모두 들어있는 자바스크립트 파일을 의미함. 이렇게 하나의 파일에 작성하는 것을 덕스(Ducks) 패턴

ahnanne.tistory.com

위 두 개의 사이트에서 실제 파일 정리에 도움을 많이 받았다.

Ducks 패턴을 적용한 파일구조는 아래와 같다.

덕스 패턴 적용한 파일구조

 

가장 먼저, `action.js`와 `reducer.js`의 action, reducer를 합쳐

새로운 파일 `transactions.js`을 만들었다. 

// src/modules/transactions.js

/* ----------------- 액션 타입 ------------------ */
export const ADD = "transactions/ADD";
export const EDIT = "transactions/EDIT";
export const REMOVE = "transactions/REMOVE";

/* ----------------- 액션 생성 함수 ------------------ */
export const addTransaction = (transaction) => ({
  type: ADD,
  payload: transaction,
});

export const editTransaction = (transaction) => ({
  type: EDIT,
  payload: transaction,
});

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

/* ----------------- 모듈의 초기 상태 ------------------ */
const initialState = {
  transactions: [],
};

/* ----------------- 리듀서 ------------------ */
const transactionReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD:
      return {
        ...state,
        transactions: [...state.transactions, action.payload],
      };
    case EDIT:
      return {
        ...state,
        transactions: state.transactions.map((transaction) =>
          transaction.id === action.payload.id ? action.payload : transaction,
        ),
      };
    case REMOVE:
      return {
        ...state,
        transactions: state.transactions.filter((transaction) => transaction.id !== action.payload),
      };
    default:
      return state;
  }
};

export default transactionReducer;

 

// App.js

import React from "react";
import AppRouter from "./routes/Router";

function App() {
  return <AppRouter />;
}

export default App;

`App.js`에서 store를 전역 관리해 주던 코드(`<Provider>` 사용한 코드)를 제거하고

store를 생성하던 파일인 `store.js` 파일도 삭제했다.

이후 삭제한 모든 코드를 최상위 코드인 `index.js`로 옮겼다.

👇

// index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { createStore } from "redux";
import transactionReducer from "./modules/transactions";

const store = createStore(transactionReducer);

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

 

✅덕스패턴 적용까지 완료

 

 

정의된 액션 생성자 함수로 리덕스 스토어의 값을 어떻게 가져와 사용했는지는 다음에 포스팅하도록 하겠다. 

 

 

 


참고

 

https://lunit.gitbook.io/redux-in-korean

 

Read Me | Redux

http://ko.redux.js.org

lunit.gitbook.io

https://react.vlpt.us/redux/

 

6장. 리덕스 · GitBook

이번 챕터에서 알아볼 주제는 리덕스(Redux) 입니다. 리덕스는 리액트 생태계에서 가장 사용률이 높은 상태관리 라이브러리입니다. 리덕스를 사용하면 여러분이 만들게 될 컴포넌트들의 상태 관

react.vlpt.us

https://wonit.tistory.com/344

 

[Redux] 리덕스를 구성하는 5개의 핵심 Action, Reducer, Store, Dispatch, Subscribe

오늘은 리덕스의 핵심 멤버인 5형제를 알아보자. Action Reducer Store Dispatch Subscribe 얘들을 알아볼 것인데 일단 오늘 가장 중요한 것은 흐름을 알아야한다. 다른 예제들과 다르게 이번 글에 나오는

wonit.tistory.com

 

반응형