티스토리 뷰

반응형

Redux는 JavaScript 애플리케이션의 상태 관리 라이브러리이다. React에서 Redux를 사용하는 주된 이유는 상태 관리의 효율성과 예측 가능성을 높이기 위함이다. React 차체에서도 컴포넌트 state를 통한 상태 관리가 가능하지만, 복잡한 상태 관리가 필요한 대규모 애플리케이션에서는 Redux와 같은 전역 상태 관리 방식을 도입한다.

 

Redux 사용 이유

중앙 집중식 상태 관리와 단일 출처

Redux는 상태를 하나의 스토어에 저장한다. 때문에 한 곳에서 모든 상태 관리가 이루어진다. 이는 데이터가 어디서 오는지 명확하게 파악할 수 있게 하며, 데이터의 일관성을 유지할 수 있다.

예측 가능성

Redux는 상태의 변화를 예측 가능한 방식으로 다룬다. 상태는 불변성을 유지하면서 액션에 의해 변경되므로, 특정 액션이나 이벤트에 대한 상태 변화를 예측하기 쉽다.

상태 불변성

Redux는 상태를 불변 객체로 다루기를 권장한다. 상태를 직접 변경하는 것이 아니라 새로운 상태를 반환하는 방식으로 상태를 관리하게끔 한다. 불변성을 유지하면 상태 변화를 추적하고 이전 상태와 현재 상태 간의 차이를 쉽게 계산할 수 있다.

디버깅 용이성

Redux는 미들웨어와 개발자 도구를 통해 상태 변화를 추적할 수 있다.

미들웨어 지원

미들웨어를 통해 액션 디스패치 중간에 특정 작업을 수행할 수 있다. 이를 통해 비동기 작업, 로깅, 라우팅 등 다양한 기능을 구현할 수 있다.

 

 

Redux 주요 용어 (카페 주문 시스템 비유)

스토어 (Store)

전체 주문 목록이나 주문 관련 모든 데이터를 포함하는 곳

리듀서 (Reducer)

특정 종류의 주문을 처리하는 점원. 예를 들어, 아메리카노를 만드는 점원, 라떼를 만드는 점원 등으로 설명할 수 있다. 주문이 들어오면 해당 주문에 따라 레시피에 맞게 음료를 제조하고, 주문 상태를 업데이트한다.

디스패치 (Dispatch)

주문 요청을 적절한 주문 처리 직원에게 보내는 행위

액션 (Action)

주문 요청. "아메리카노 주문" 또는 "라떼 주문 취소"와 같이 주문에 대한 요청이 액션에 해당한다.

불변성 (Immutability)

기존 주문을 수정하는 대신에 새로운 목록을 생성하여 업데이트

구독(Subscribe)

주문 상태 변경을 감지하여 화면에 자동으로 반영하는 행위

미들웨어(Middleware)

주문 처리 전에 특정 동작이나 조건을 확인하는 단계. 예를 들어, 주문을 받기 전에 할인 쿠폰이 적용되었는지 확인하는 행위.

액션 크리에이터(Action Creator)

특정 주문 요청에 대한 액션 객체를 생성. 예를 들어 메뉴판에 나열된 음료 중에서 특정 음료를 선택하면 그 음료에 대한 주문이 요청된다.

 

 

Redux 데이터 흐름

View ⇒ Dispatch ⇒ (Middleware) ⇒ Reducer ⇒ New State! ⇒ Store ⇒ Components

카페 주문 상황 ⇒ 아메리카노 주문할게요! ⇒ (할인쿠폰 있나요?) ⇒ 직원이 주문 내역 업데이트 ⇒ 새로운 주문 내역 생성! ⇒ 주문 대기 목록 업데이트 ⇒ 주문 대기 화면에 표시

 

 

Redux 설치와 코드 예제

github: https://github.com/fromnowwon/redux-cafe-order-system
Vanilla JS 예제: https://fromnowwon.tistory.com/entry/Vanilla-JavaScript-Redux

설치

npm install redux react-redux

 

 

코드 예제 (리팩토링 ver.)

실무에 더 가까운 버전으로 리팩토링 하였다. (기존 버전은 아래에)

 

변경 사항

1. 구조 정리

components, redux로 나누어 정리하였고, redux 내에서도 기능에 따라 폴더를 나누었다.

/redux-cafe-order-system
├── src
│   ├── components
│   │   ├── CafeOrderSystem.js
│   └── redux
│       ├── order
│       │   ├── actions.js
│       │   ├── reducer.js
│       │   └── types.js
│       ├── index.js
│       ├── rootReducer.js
│       └── store.js
└── package.json

 

2. reducer 통합

모든 reducer를 rootReducer로 통합하였다.

 

3. 미들웨어 사용

미들웨어인 redux-logger를 사용해서 state 변화를 추적할 수 있도록 했다.

 

4. 주문 총 개수 출력

 

 

 

주요 개념을 실생활과 연결하면 이해하기 쉽다.

  • store: 스토어 (주문목록)
  • actions: 액션 (요구사항)
  • reducers: 리듀서 (점원)
  • CafeOrderSystem: 주문 화면 컴포넌트

 

components/CafeOrderSystem.js

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addOrder, cancelOrder } from '../redux';

const CafeOrderSystem = (props) => {
  const dispatch = useDispatch();
  const orderList = useSelector(state => state.orders.orderList);

  const handleAddOrder = (item) => {
    dispatch(addOrder(item, 1));
  };

  const handleCancelOrder = (orderId) => {
    dispatch(cancelOrder(orderId));
  };

  return (
    <div>
      <div>
        <h3>음료를 선택해주세요</h3>
        <button onClick={() => handleAddOrder('아메리카노')}>아메리카노</button>
        <button onClick={() => handleAddOrder('카페 라떼')}>카페 라떼</button>
        <button onClick={() => handleAddOrder('카라멜 마키아또')}>카라멜 마키아또</button>
        <button onClick={() => handleAddOrder('바닐라 라떼')}>바닐라 라떼</button>
      </div>
      <div>
        <h3>주문 리스트</h3>
        <span>(총 {orderList.length}잔)</span>
        <ul>
          {orderList.map(order => (
            <li key={order.id}>
              {order.item} (수량: {order.quantity})
              <button onClick={() => handleCancelOrder(order.id)}>주문 취소</button>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default CafeOrderSystem;

 

redux/index.js

// 모든 액션 통합하여 내보내기
export { addOrder, cancelOrder } from './order/actions';

 

redux/store.js

import { createStore, applyMiddleware } from 'redux';
import rootReducer from './rootReducer';
import logger from 'redux-logger';

// 여러 middleware 배열로 관리
const middleware = [logger];

// rootReducer를 사용하는 스토어 생성
const store = createStore(rootReducer, applyMiddleware(...middleware));

export default store;

 

redux/rootReducer.js

import { combineReducers } from "redux";
import orderReducer from "./order/reducer";

// 모든 리듀서 통합
const rootReducer = combineReducers({
  orders: orderReducer,
})

export default rootReducer;

 

redux/order/reducer.js

import { ADD_ORDER, CANCEL_ORDER } from './types';

// 초기 상태 정의
// => 주문이 없는 상태
const initialState = {
  orderList: [],
};

// 리듀서 함수 정의
// => 점원의 역할 정의
const orderReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_ORDER:
      console.log('ADD_ORDER', state)
      return {
        ...state,
        orderList: [
          ...state.orderList,
          {
            id: state.orderList.length + 1,
            item: action.payload.item,
            quantity: action.payload.quantity,
          },
        ],
      };
    case CANCEL_ORDER:
      return {
        ...state,
        orderList: state.orderList.filter(order => order.id !== action.payload.orderId),
      };
    default:
      return state;
  }
};

export default orderReducer;

 

redux/order/actions.js

import { ADD_ORDER, CANCEL_ORDER } from "./types";

// 액션 생성 함수 정의
// => 요구 사항에 대한 동작
export const addOrder = (item, quantity) => ({
  type: ADD_ORDER,
  payload: { item, quantity },
});

export const cancelOrder = (orderId) => ({
  type: CANCEL_ORDER,
  payload: { orderId },
});

 

redux/order/types.js

// 액션 타입 정의
// => 요구 사항 정의
export const ADD_ORDER = 'ADD_ORDER';
export const CANCEL_ORDER = 'CANCEL_ORDER';

 

 


코드 예제 (기존 ver.)

프로젝트 구조

redux-cafe-order-system
├── src
│   ├── actions.js
│   ├── reducer.js
│   ├── store.js
│   └── CafeOrderSystem.js
└── package.json

 

store.js

import { createStore } from 'redux';
import orderReducer from './reducers';

// orderReducer를 사용하는 스토어 생성
// => 담당 점원의 주문 목록 생성
const store = createStore(orderReducer);

export default store;

 

actions.js

// 액션 타입 정의
// => 요구 사항 정의
export const ADD_ORDER = 'ADD_ORDER';
export const CANCEL_ORDER = 'CANCEL_ORDER';

// 액션 생성 함수 정의
// => 요구 사항에 대한 동작
export const addOrder = (item, quantity) => ({
  type: ADD_ORDER,
  payload: { item, quantity },
});

export const cancelOrder = (orderId) => ({
  type: CANCEL_ORDER,
  payload: { orderId },
});

 

reducers.js

import { ADD_ORDER, CANCEL_ORDER } from './actions';

// 초기 상태 정의
// => 주문이 없는 상태
const initialState = {
  orders: [],
};

// 리듀서 함수 정의
// => 점원의 역할 정의
const orderReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_ORDER:
      return {
        ...state,
        orders: [
          ...state.orders,
          {
            id: state.orders.length + 1,
            item: action.payload.item,
            quantity: action.payload.quantity,
          },
        ],
      };
    case CANCEL_ORDER:
      return {
        ...state,
        orders: state.orders.filter(order => order.id !== action.payload.orderId),
      };
    default:
      return state;
  }
};

export default orderReducer;

 

CafeOrderSystem.js

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addOrder, cancelOrder } from './actions';

const CafeOrderSystem = () => {
  const dispatch = useDispatch();
  const orders = useSelector(state => state.orders);

  const handleAddOrder = (item) => {
    dispatch(addOrder(item, 1));
  };

  const handleCancelOrder = (orderId) => {
    dispatch(cancelOrder(orderId));
  };

  return (
    // 주문 화면
    <div>
      <div>
        <h3>음료를 선택해주세요</h3>
        <button onClick={() => handleAddOrder('아메리카노')}>아메리카노</button>
        <button onClick={() => handleAddOrder('카페 라떼')}>카페 라떼</button>
        <button onClick={() => handleAddOrder('카라멜 마키아또')}>카라멜 마키아또</button>
        <button onClick={() => handleAddOrder('바닐라 라떼')}>바닐라 라떼</button>
      </div>
      <div>
        <h3>주문 리스트</h3>
        <ul>
          {orders.map(order => (
            <li key={order.id}>
              {order.item} (수량: {order.quantity})
              <button onClick={() => handleCancelOrder(order.id)}>주문 취소</button>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default CafeOrderSystem;

 

 

 

 

React에 Redux를 도입하면 복잡한 애플리케이션의 상태 관리를 효율적으로 할 수 있다. Redux를 사용할지 여부는 프로젝트의 규모와 복잡도, 상태 관리의 필요성 등에 따라 결정하면 된다.

 

 

반응형
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31