오늘은 저번시간 배운 리덕스 개념을 실제 프로젝트에 적용해보자 ~! 🙌
이번 편은 저번 시간 배운 리덕스 개념을 실제 적용해보는 시간으로
리덕스 개념 를 먼저 보시는 것을 추천합니다
- redux-exercise 에서 주소 복사
git clone 해당주소
출처 : 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
폴더를 만들고 그 아래 actions
와 reducers
폴더를 만든다. 폴더 구조는 아래와 같다.
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,
};
};
여기서
addCart
와deleteCart
는 액션 그 자체가 아닌 액션생성함수이다. 액션생성함수가 return하는 객체가 바로 액션객체이다.
리듀서는 액션생성함수가 리턴한 액션객체를
dispatch
했을 때 들어오는 곳으로 store를 업데이트 시켜주는 함수이다.
switch문
을 통해 action type
에 따라 실행될 함수를 정의해준다.state
을 인자로 받고 두 번째 인자로는 action
을 받는다.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와 연결시켜 준다.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
-> CartNoti
로 itemCount={cartItems.length}
를 props로 내려주고 있다.
하지만 리덕스를 적용해주면 해당 정보가 필요한 컴포넌트에서 스토어로 바로 접근하여 데이터를 받아올 수 있다.
useSelector를 적용해서 store에 접근이 가능하다
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
를 만나 액션타입에 따라 우리가 정의해둔 동작을 실행하게 된다. 그 동작으로 스토어가 업데이트 되며 그 스토어를 바라보고 있는 컴포넌트들이 업데이트가 된다.
혹시 이건 실제 상품화 된 웹인가요 아니면 그냥 개인용 연습 프로젝트인가요?
이 프로젝트와 관련하여 강의 글이라던지 따라치기라던지 계획이 있으신지요.
리엑트와 리덕스를 이제 막 배웠는데 잘 감이안와 여쭤봅니다.