import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './store/store';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
를 import하고, state값의 공유를 원하는 컴포넌트를 다 감싼다.그리고 Redux에서 사용하는 오직 하나의 저장소 store를 할당한다
위에서보면, App 컴포넌트와 그 안에 있는 모든 HTML, 컴포넌트들은 전부 state를 직접 props 전송없이 사용할 수 있게된다.
import { createStore } from 'redux';
import rootReducer from '../reducers/index'
const store = createStore(rootReducer)
export default store;
말 그대로 store state가 관리되는 오직 하나뿐인 저장소의 역할을 합니다. Redux 앱의 state가 저장되어 있는 공간이다. createStore 메서드를 활용해 reducer를 연결시킨다 createStore와 더불어 다른 리듀서의 조합을 인자로 넣어서 스토어를 생성할 수 다.
import { combineReducers } from 'redux';
import ItemReducer from './ItemReducer';
const rootReducer = combineReducers({
ItemReducer
});
export default rootReducer;
import { ADD_CART, REMOVE_CART, SET_QUANTITY } from "../actions/index"
import { initialState } from '../asset/data'
console.log(initialState);
const ItemReducer = (state = initialState, action:any) => {
return state;
}}
export default ItemReducer
index 파일 안에 reducer들을 관리 할수가 있는데 combineReducers을 통해 한번에 관리가 가능하다 현재 ItemReducer components만 사용중이다.
Action을 통해 받아 state를 ItemReducer에서 새로운 State를 반환한다.
//액션 타입 선언
export const ADD_CART = "ADD_CART";
export const REMOVE_CART = "REMOVE_CART";
export const SET_QUANTITY ="SET_QUANTITY"
//액션 생성 선언 함수
export const AddCart = (itemId:number) => {
console.log(itemId);
return {
}}}
export const RemoveCart = (itemId:number) => {
console.log(itemId);
return {
}}}
export const SetQuantity = (quantity:number, itemId:number) => {
console.log(quantity, itemId);
return {
}}}
버튼을 클릭 하였을 때 action을 취하게 된다 액션 타입을 선언하고 액션 생성 함수를 구성한다. type은 필수로 지정을 해 주어야 한다. 초기 큰틀에서 설정하는 것이 어려웠다. 버튼을 클릭 하였을 때 부터 시작하는게 맞는건지 사실 그렇게 해도 설정은 됐을 것 같지만 index 컴포넌트에서 provider로 감싸는 것 부터 시작하여 store={store}를 할당하며 에러 발생한 것부터 역순 방향으로 해결해 나가며 구성하니 해결이 되었다.
초기 구성은 다 하였다 그럼 이제
그림 과 같은 패턴으로 흐름 기억하며 만들어 보도록 한다.
메인 페이지에는 아이템 리스트들은 보여주며 클릭 하였을 때 장바구니로 추가가 되고 장바구니에 추가되는 기능을 갖고 있다.
import React from 'react';
import { AddCart } from '../actions';
import { useSelector, useDispatch } from 'react-redux';
import './Main.css';
export interface ItemReducer {
ItemReducer : Array<object>
}
export interface DataSetting {
items: [{
price:number;
id:number;
img:string;
name:string;
text:string;
}]
cartItems: [{
quantity:number;
itemId:number;
}];
}
const Main = () => {
const state:any = useSelector<ItemReducer>(state=> state.ItemReducer);
const dispatch = useDispatch();
const {items, cartItems}:DataSetting = state;
console.log({items, cartItems})
const AddCartSetting = ( itemId:number ) => {
const find = cartItems.filter((ele):boolean => (ele.itemId === itemId))[0]
if(!find) {
console.log(find);
console.log('새로운 상품 추가')
dispatch(AddCart(itemId))
}
else {
console.log('기존 리스트와 일치하는 상품')
}
}
return (
<div className="MainBox">
{items.map((item) => (
<div className="Box" key={item.id}>
<img className="ImgBox" src={item.img} alt="" />
<div className="ItemInfo">{item.name} {item.price}원</div>
<span className='Text'>{item.text}</span>
<div className='BtnBox'>
<button className="ItemBtn" onClick={() => AddCartSetting(item.id)}>장바구니 추가</button>
</div>
</div>
))}
</div>
)
}
export default Main;
일단 처음에는 내장 되어있는 데이터를 갖고 와야 되는 것인데 그것은 오직 하나 뿐인 저장소 store에 보관 되어 있다. 이것을 가져 올 때 props를 해줄 필요 없이 useSelector를 통해 어디서든 쉽게 가져 올수가 있다.
const state:any = useSelector<ItemReducer>(state=> state.ItemReducer);
가져 온 값을 map 메서드로 출력해주면 된다. 이 부분은 다른 점이 없다 그리고 이제 버튼 을 클릭 하였을 때 아이템이 추가 되는 메서드를 실행 시켜야 하는데 더 이상 useState를 사용하지 않기 때문에 상태 변경을 하려면 Action에서 실행하고자 지정한 타입을
const dispatch = useDispatch(); useDispatch()를 통해 reducer로 보내주게 된다
그러면 이제 Action 타입을 구성할 차례이다
export const AddCart = (itemId:number) => {
console.log(itemId);
return {
type: ADD_CART,
payload : {
quantity: 1,
itemId
}
}
}
액션 생성 함수를 보게 되면 아이템의 아이디를 받아오게 되며 타입을 지정해 주면 된다.
그럼 이제 reducer를 보도록 해보면
useDispatch를 통해 가져온 값은 type의 값과 payload의 값을 확인 할 수 있다.
payload 값을 cartItems에 ...state.cartItems를 통해 복사해 온 후에 객체 값으로 넣어주고 Object.assign()를 통해 기존 객체 값들과 합쳐 주면 된다.
Object.assign()에 대한 설명은 Redux 블로깅에 따로 정리를 해두엇다.
const ItemReducer = (state = initialState, action:any) => {
console.log(state);
console.log(action);
switch (action.type) {
case ADD_CART:
return Object.assign({}, state, { cartItems: [...state.cartItems, action.payload]})
case REMOVE_CART:
return Object.assign({}, state, {
cartItems: state.cartItems.filter((ele:any) => ele.itemId !== action.payload.itemId )
})
case SET_QUANTITY:
let idx = state.cartItems.findIndex((ele:any) => ele.itemId === action.payload.itemId)
return Object.assign({}, state, {
cartItems: [...state.cartItems.slice(0, idx), action.payload,
...state.cartItems.slice(idx + 1)]
});
default:
return state;
}
}