리덕스 적용해서 장바구니 기능 만들기!

이병수·2020년 11월 22일
1

Redux

목록 보기
2/2
post-thumbnail
post-custom-banner

오늘은 저번시간 배운 리덕스 개념을 실제 프로젝트에 적용해보자 ~! 🙌

들어가며

이번 편은 저번 시간 배운 리덕스 개념을 실제 적용해보는 시간으로
리덕스 개념 를 먼저 보시는 것을 추천합니다


프로젝트 가져오기

출처 : wecode github


리덕스 적용 전


리덕스 적용 전 폴더 구조는 위와 같다.

//injex.js
ReactDOM.render(
  <React.StrictMode>
    <Routes />
  </React.StrictMode>,
  document.getElementById("root")
);
//Route.js
function Routes() {
  const [cartItems, setCartItems] = useState(CART_ITEM);

  useEffect(() => {
    console.log("Routes render");
  });

  const filterItem = (idx) => {
    setCartItems(
      cartItems.filter((_, i) => {
        return i !== idx;
      })
    );
  };

  const addToCart = (item) => {
    alert("장바구니에 추가되었습니다.");
    setCartItems([...cartItems, item]);
  };

  return (
    <Router>
      <Nav itemCount={cartItems.length} />
      <Switch>
        <Route
          exact
          path="/"
          component={() => <ProductList addToCart={addToCart} />}
        />
        <Route
          exact
          path="/cart"
          component={() => (
            <CartList cartItems={cartItems} filterItem={filterItem} />
          )}
        />
      </Switch>
    </Router>
  );
}

export default Routes;

리덕스를 쓰지 않을 시 위와 같이 최상단 부모 컴포넌트인 Route에서 모든 데이터와 함수를 뿌려주는 형식이다.

문제점

  • 장바구니 담기를 클릭 했을 때 Route.js가 업데이트 되면서 연결된 컴포넌트 전체가 재렌더링 되는 문제가 발생한다.

  • 또한 저번 리덕스 시간에 배웠듯 하나의 장바구니 상태를 여러 컴포넌트에 걸쳐 연동시키고 싶으나 계속되는 상태 및 함수의 props 전달로 인해 복잡해지는 문제가 발생한다.

이때 리덕스를 도입하면 위 문제를 깔끔히 해결할 수 있다.

리덕스 설치 및 폴더구조

  • 우선 리덕스를 설치해보자
    npm install redux react-redux --save

  • 그리고 src폴더 아래 store 폴더를 만들고 그 아래 actionsreducers 폴더를 만든다. 폴더 구조는 아래와 같다.

src
├── Components
│   ├── CartItem.js
│   ├── CartNoti.js
│   ├── ProductCard.js
│   └── Nav.js
├── Pages
│   ├── CartList.js // 상품 리스트 페이지
│   └── ProductList.js // 장바구니 페이지
├── Routes.js
├── index.js
└── store
    ├── actions
    │   └── index.js // 액션 객체들
    └── reducers
        ├── cartReducer.js // 개별 리듀서
        └── index.js // 루트 리듀서

액션 & 리듀서

액션

제일 처음으로 우리가 변화를 주고자 하는 액션을 정의해준다. 장바구니에서는 카트 담기(ADD_CART)와 카트에서 제거하기(DELETE_CART) 두 가지 액션이 있는데 이것을 정의해주고 export 해준다.

export const addCart = (item) => {
  return {
    type: "ADD_ITEM",
    payload: item,
  };
};

export const deleteCart = (items) => {
  return {
    type: "DELETE_ITEM",
    payload: items, 
  };
};

여기서 addCartdeleteCart는 액션 그 자체가 아닌 액션생성함수이다. 액션생성함수가 return하는 객체가 바로 액션객체이다.

리듀서

리듀서는 액션생성함수가 리턴한 액션객체를 dispatch 했을 때 들어오는 곳으로 store를 업데이트 시켜주는 함수이다.

  • 리듀서는 switch문 을 통해 action type에 따라 실행될 함수를 정의해준다.
  • 첫 번째 인자는 store의 state을 인자로 받고 두 번째 인자로는 action을 받는다.
  • 미리 정의해둔 목데이터를 초기 state(store) 값으로 넣는다.
const cartReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case "ADD_ITEM":
      return [...state, action.payload]; // 불변성을 위해 ...state 을 해준다. 
    case "DELETE_ITEM":
      return [...action.payload]; // DELETE는 store를 갈아 끼울 것이므로 기존 ...state을 해주지 않는다. 
    default: 
      return state;  // 두 타입이 아닐 경우 (에러 방지를 위해) state을 반환하는 것으로 설정해준다. 
  }
};

export default cartReducer;

ADD_ITEM에서 리턴하는 action.payload는 추가하는 한 아이템(상품)이다. 그러므로

{ price : 10,000,
product_name : "T-Shirt" }

위와 같은 객체가 들어간다고 보면 된다.

루트 리듀서

이번 예의 경우 리듀서를 하나만 작성하지만 대부분의 프로젝트에서는 하나 이상의 리듀서를 만드는 경우가 많다. 이때 여러개의 리듀서를 합쳐서 스토어와 연결시켜줘야 한다.

  • 리듀서를 합치는 것은 리덕스에서 제공하는 combineReducers를 이용한다.
import { combineReducers } from "redux";
import cartReducer from "./cartReducer";

export default combineReducers({
  cartReducer  // cartReducer : cartReducer 의 단축속성이다. 
}) 

하나 이상의 리듀서를 합쳐줘야 할 때는 아래와 같이 써주면 된다

export default combineReducers({
  cartReducer, asdadReducer 
}) 

스토어 생성

작성한 rootReducer를 실제 프로젝트와 연결시키기 위한 작업으로 최상단의 index.js에서 루트 리듀서와 연결된 store를 생성 후 연결시켜준다.

  • 리덕스에서 제공하는 createStore를 통해 store를 생성하는데 이때 인자로 rootReducer를 넣어줘 store와 연결시켜 준다.
  • 이때 리듀서와 연결된 store는 react-redux 에서 제공하는 Provider 컴포넌트에 store 속성값으로 넣어줘야 한다.
import React from "react";
import ReactDOM from "react-dom";
import Routes from "./Routes";
import { createStore } from "redux";
import rootReducer from "./store/reducers";
import { Provider } from "react-redux";
const store = createStore(rootReducer); 

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

);	

스토어 연결

장바구니 담긴 개수


현재 위와 같이 카트에 담긴 개수를 알기 위해서
Route -> Nav -> CartNotiitemCount={cartItems.length} 를 props로 내려주고 있다.

하지만 리덕스를 적용해주면 해당 정보가 필요한 컴포넌트에서 스토어로 바로 접근하여 데이터를 받아올 수 있다.

useSelector를 적용해서 store에 접근이 가능하다

  • react-redux에서 제공하는 useSelector를 import 한다
    import { useSelector } from "react-redux";
  • useSelector를 활용해 스토어에 바로 접근이 가능한데 접근할 때 우리가 만든 리듀서를 키값으로 접근한다.
    const items = useSelector((store) => store.cartReducer);


위 item를 콘솔로 찍으면 스토어에 담긴 정보를 이렇게 불러온다.

장바구니 추가

장바구니에 추가하기 위해서는 현재의 구조에서는 최상단 컴포너트인 Route 컴포넌트에서 정의한 addToCart 함수를 prop로 <ProductList> -> <ProductCard> 로 함수를 내려줘 장바구니 기능을 구현하고 있다.

하지만 리덕스에서는 useDispatch를 사용하여 액션 객체를 바로 보낼 수 있다!

  • 리액트 리덕스에서 제공하는 useDispatch를 임포트한다.

    import { useDispatch } from "react-redux";
    
    const dispatch = useDispatch(); 
  • dispatch는 액션 객체를 담아 보내야 하므로 액션 함수를 import 한 후 클릭 이벤트가 실행될 때 dispatch 함수가 실행되게 만든다

    <AddCartBtn onClick={() => dispatch(addCart(item))}>

    addCart라는 함수가 dispatch를 통해 store로 보내지는데 그 전에 reducer를 만나 액션타입에 따라 우리가 정의해둔 동작을 실행하게 된다. 그 동작으로 스토어가 업데이트 되며 그 스토어를 바라보고 있는 컴포넌트들이 업데이트가 된다.

post-custom-banner

1개의 댓글

comment-user-thumbnail
2021년 5월 11일

혹시 이건 실제 상품화 된 웹인가요 아니면 그냥 개인용 연습 프로젝트인가요?
이 프로젝트와 관련하여 강의 글이라던지 따라치기라던지 계획이 있으신지요.
리엑트와 리덕스를 이제 막 배웠는데 잘 감이안와 여쭤봅니다.

답글 달기