컴포넌트간 값의 전달을 props 로만 하다보면 복잡도가 상승하여 머리가 지끈해지게 됩니다. 이럴 때 redux 를 도입하면 이러한 복잡성을 해결할 수 있습니다.
Redux 를 공부하기 위해 간단한 예제를 만들었습니다. 상품을 장바구니에 담으면 nav 바에 장바구니 아이콘 옆 숫자가 늘어나고, 장바구니 페이지에서 상품 삭제 아이콘을 누르면 상품이 곧바로 삭제되며 nav 바의 숫자 또한 줄어들게 하는 것을 목표로 하였습니다.
원하시는 분은 아래 주소에서 repo 를 clone 받아 공부하셔도 좋습니다.
src
|
|-store
|
|-actions
| |
| |-index.js
|
|-reducers
|
|-index.js
|-cartReducer.js
라우터까지는 구현이 되어있으니 npm install
하시고 시작하시면 됩니다.
npm i redux react-redux
도 설치해주세요.
actions 폴더의 index.js 에서 액션을 생성하는 액션 생성 함수를 정의해 줍니다.
dispatch
메서드에 넣어서 호출합니다. (useDispatch
)// store -> actions -> index.js
export const addCart = (item) => {
return {
type: "ADD_ITEM",
payload: item
}
}
// type 이라는 속성을 가진 액션을 생성하는 addCart 는 액션 생성 함수입니다.
// 이름만 봐도 장바구니에 담는 역할을 할 것 같습니다.
export const deleteCart = (items) => {
return {
type: "DELETE_ITEM",
payload: items
}
}
reducers 폴더의 cartReducer.js 파일에서 장바구니와 관련된 reducer 함수를 작성해 줍니다. 프로젝트가 크다면 서로 다른 주제별로 reducer 를 만들 수 있습니다. 이렇게 다른 주제의 reducer 들은 결국 rootReducer 로 모입니다.
// store -> reducers -> cartReducer.js
const cartReducer = (state = [], action) => {
switch (action.type) {
case "ADD_ITEM":
return [...state, action.payload];
case "DELETE_ITEM":
return [...action.payload];
default:
return state;
}
}
export default cartReducer;
// store -> reducers -> index.js
// 이곳이 rootReducer 입니다.
import { combineReducers } from "redux";
import cartReducer from "./cartReducer";
export default combineReducers({ cartReducer });
// redux 에서 제공하는 combineReducers 를 통해 주제별로 나눈 조각 reducer (여기서는 cartReducer 만 존재) 들을 rootReducer 로 모아줍니다.
이렇게 모아진 rootReducer 는 전체 파일을 렌더하고 있는 index.js 에서 사용할 수 있습니다.
// src -> index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Routes from './Routes';
import reportWebVitals from './reportWebVitals';
import { Provider } from "react-redux";
import { createStore } from "redux";
import rootReducer from "./store/reducers/"
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<Routes />
</Provider>
,
document.getElementById('root')
);
reportWebVitals();
// redux 에서 제공하는 createStore 틍해 우리가 만든 rootReducer 를 import 받아와 store 라는 // 이름으로 저장합니다.
// react-redux 에서 제공하는 Provider 를 통해 프로젝트 전역에서 바라볼 수 있도록 해줍니다.
ProductItem.js 컴포넌트에너 useDispatch 와 actions 에서 작성해 둔 액션 생성함수를 import 받아와 사용합니다. 현재 ProductItem 컴포넌트는 부모 컴포넌트에서 map 을 돌리고 있습니다. props 로 내려오는 선택한 item 에 관한 데이터를 "장바구니 담기" 버튼을 클릭하면 액션 생성 함수 addCart 의 인자로 넘겨줍니다.
아래와 같이 action 으로 값을 올려줄 때에는 useDispatch
라는 훅을 사용합니다.
이렇게 하면 장바구니에 담기로 선택한 아이템에 관한 데이터가 actions 의 addCart 를 거쳐 reducer 에서 switch 문을 만납니다. switch 문에서는 action 의 type 이 "ADD_CART" 라면 기존의 state 에 전달 받은 데이터 payload 를 추가시킵니다.
이렇게 store 가 업데이트되면 해당 store 를 구독하고 있는 컴포넌트의 값은 저절로 바뀝니다.
현재 상품 리스트 페이지에서 각 상품들을 장바구니에 담으면 store 에 하나씩 업데이트 됩니다. 이렇게 업데이트 된 store 를 구독하는 법을 알아봅시다. store 를 구독하려면 useSelector
라는 훅이 필요합니다.
useSelector
를 사용하여 store 에 접근하여 store 의 조각인 cartReducer 즉, 장바구니 담기 버튼을 클릭할 때마다 업데이트 되는 장바구니에 담긴 상품들의 배열을 cart 라는 변수에 담아 아래에서 map 을 돌려주면서 장바구니를 구현합니다.
현재 actions 의 addCart 라는 액션 생성함수를 이용해서 장바구니에 상품을 추가하는 기능을 구현하였습니다. 이와 같은 원리로 Nav 컴포넌트도 store 를 구독하게 한다면 store 가 업데이트 될 떄마다 장바구니 갯수가 자동으로 업데이트 됩니다.
장바구니의 상품을 삭제하는 기능 역시 같은 원리로 filter 를 사용한다면 어렵지 않게 구현할 수 있습니다.