지난 시간에는 redux를 저장하고 사용하는 방법을 알아봤다. 이번시간에는 수정하는 방법을 알아보자!
redux를 사용하면서 state 데이터를 수정하고 싶을 때는
- reducer 함수를 만들고 그곳에 데이터 수정하는 방법을 정의
- 원하는 곳에서 dispatch() 라는 함수를 써서 reducer에게 수정해달라고 요청
장바구니 품목에 + / - 버튼 만들기
(Cart.js)
function Cart(props){
return (
<div>
<Table responsive>
<tr>
<th>#</th>
<th>상품명</th>
<th>수량</th>
<th>변경</th>
</tr>
{ props.state.map((a,i)=>{
return (
<tr key={i}>
<td>{a.id}</td>
<td>{a.name}</td>
<td>{a.quan}</td>
<td><button onClick={()=>{ ??? }}> + </button></td>
</tr>
)
}) }
</Table>
</div>
)
}
map 반복문을 적용해서 장바구니 항목이 여러개면 <tr>을 여러개 생성
reducer 만들기
reducer는 function안에
- state 초기값과
- state 데이터 수정방법이 잔득 들어있는 함수이다.
간단하게 state를 퉤 뱉어내는 함수로, reducer 만든걸 createStore()안에 넣으시면 reducer가 완성이다.
(index.js)
function reducer(){
return [{id : 0, name : '멋진신발', quan : 2}]
}
let store = createStore(reducer);
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App/>
</Provider>
</BrowserRouter>
</React.StrictMode>
);
또는 이렇게 작성한다.
(index.js)
let 기본state = [{id : 0, name : '멋진신발', quan : 2}];
function reducer(state = 기본state, 액션){
return state
}
let store = createStore(reducer);
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App/>
</Provider>
</BrowserRouter>
</React.StrictMode>
);
1. 따로 state 변수를 생성
2. 그걸 reducer에 default 파라미터 문법으로 집어넣는다.
default 파라미터 문법?
함수를 만들 때 실수 또는 의도적으로 파라미터 입력을 안했을 때,
기본으로 가질 파라미터를 부여하는 것이다.
function reducer(state = 기본state, 액션){ return state }
state에는 default 파라미터로 만들어둔 기본state가 할당된다.
redux 쓰는 두번째 이유 : reducer로 데이터관리하기
reducer는 데이터 수정방법을 정의하는 곳으로, 수정 방법 코드를 작성하면 된다.
(index.js)
let 기본state = [{id : 0, name : '멋진신발', quan : 2}];
function reducer(state = 기본state, 액션){
if (액션.type === '수량증가') {
let copy = [...state];
copy[0].quan++;
return copy
} else {
return state
}
}
let store = createStore(reducer);
- '수량증가'라는 데이터 수정방법 이름을 하나 작명. (액션.type === 수정방법이름)
- if문 안에는 '수량증가'라는 요청이 들어올 경우 state 데이터 카피본을 만들어서 quan 항목에 1을 더해주고 그걸 return!
- else문 안에는 수량증가 요청이 안들어온 경우 기본 state를 퉤 뱉으라고 정의 !
state 데이터 수정하는 법 (dispatch)
(Cart.js에 있던 버튼)
<button onClick={()=>{ props.dispatch({type: '수량증가'}) }}> + </button>
수량증가' 라고 작명해놓은 데이터 수정방법을 실행해주세요~는 dispatch() 함수를 이용!
dispatch()를 쓰시면 HTML 안에서 reducer함수를 동작시킬 수 있다.
예제) 빼기 버튼과 기능 구현
<내가 짠 코드>
let defaltState = [
{ id: 0, name: "멋진스티커", quan: 2 },
{ id: 1, name: "예쁜스티커", quan: 4 },
];
function reducer(state = defaltState, action) {
let copy = [...defaltState];
if (action.type === "수량증가") {
copy[0].quan++;
return copy;
} else if (action.type === "수량감소") {
let copyQuan = copy[0].quan;
0 >= copyQuan ? (copyQuan = 0) : copy[0].quan--;
return copy;
} else return state;
}
let store = createStore(reducer);
ReactDOM.render(
<React.StrictMode>
<HashRouter>
<Provider store={store}>
<App />
</Provider>
</HashRouter>
</React.StrictMode>,
document.getElementById("root")
);
<tbody>
{props.state.map((a, i) => {
return (
<tr key={i}>
<td>{i}</td>
<td>{a.name}</td>
<td>{a.quan}</td>
<td>
<button
onClick={() => {
props.dispatch({ type: "수량증가" });
}}
>
+
</button>
</td>
<td>
<button
onClick={() => {
props.dispatch({ type: "수량감소" });
}}
>
-
</button>
</td>
</tr>
);
})}
</tbody>
성공~!!
어려운 redux 쓰는 이유?
소규모 사이트에선 필요가 없지만 대규모 사이트에서 데이터를 한눈에 관리할 수 있어서 사용한다.
redux를 만들어 state 수정하는 방법을 미리 정해놓으시면 redux안의 reducer만 보면 된다. state 데이터가 어떻게 바뀌는지는 reducer에 전부다 정의되어 있기 때문!
Cart 페이지에 알림창 UI와 기능
(Cart.js)
function Cart(props){
return (
<div>
<Table>table부분</Table>
<div className="my-alert2">
<p>지금 구매하시면 20% 할인</p>
<button>닫기</button>
</div>
</div>
)
}
redux store에 state를 하나 더 만들려면?
이 UI를 보여주고 안보여주는 state를 redux에 state를 만들어서 이용해 볼 것이다!
일반 컴포넌트에선 useState()를 하나 더 쓰면 되지만,
redux store에선 reducer를 하나 더 쓰면 된다!
state + reducer 세트를 하나 더 만들어서 여기에 UI의 true/false 값을 저장해 볼 것이다.
(index.js)
import {createStore, combineReducers} from 'redux';
let state초기값 = [ {id : 0, name : '새로운상품', quan : 2} ];
function reducer(){
저번시간 만든 리듀서
}
let alert초기값 = true;
function reducer2(state = alert초기값, 액션){
return state
}
let store = createStore( combineReducers({reducer, reducer2}) )
reducer를 하나 더 만들었으면 store에 등록까지 해야 사용이 가능하다!
combineReducers() 라는 함수를 하나 'redux'에서 import 하고
combineReducers() 안에 모든 리듀서를 object 형식으로 쭉 담으시면 끝!
여러 개 만든 reducer 사용하기
(Cart.js)
function state를props화(state){
console.log(state);
return {
state : state,
}
}
export default connect(state를props화)(Cart);
reducer이 두개이상이면 store의 형식이 약간 달라진다.
확인해 보기 위해 state를 props화()시켜주는 함수에 console.log로 출력을 해보면 알 수 있다.

{ reducer : [], reducer2 : true } 이런 식의 자료형이 나오 기 때문에, 골라서 사용해야한다.
(Cart.js)
function Cart(props){
return (
<div>
<Table>table부분</Table>
{ props.alert열렸니 === true
? (<div className="my-alert2">
<p>지금 구매하시면 20% 할인</p>
<button>닫기</button>
</div> )
: null
}
</div>
)
}
function state를props화(state){
console.log(state);
return {
state : state.reducer,
alert열렸니 : state.reducer2 //리듀서2에 있는거 가져오는법
}
}
export default connect(state를props화)(Cart);
state.reducer 라는 데이터는 state라고 저장하고 state.reducer2라는 데이터는 alert열렸니라고 저장해서 사용한다.
(예제)닫기버튼 눌렀을 때 모달창이 닫히게 하려면?
<내가 짠 코드>
(Cart.js)
{props.close === true ? (
<div className="my-alert2">
<p>지금 구매하시면 1000원 할인 쿠폰 제공</p>
<button
onClick={() => {
props.dispatch({ type: "닫기" });
}}
>
닫기
</button>
</div>
) : null}
</div>
);
}
function state를props화(state) {
console.log(state);
return {
state: state.reducer,
close: state.reducer2,
};
}
(index.js)
let defaltState = [
{ id: 0, name: "멋진스티커", quan: 2 },
{ id: 1, name: "예쁜스티커", quan: 4 },
];
function reducer2(state = true, action) {
if (action.type === "닫기") {
return false;
} else {
return state;
}
}
function reducer(state = defaltState, action) {
let copy = [...defaltState];
if (action.type === "수량증가") {
copy[0].quan++;
return copy;
} else if (action.type === "수량감소") {
let copyQuan = copy[0].quan;
0 >= copyQuan ? (copyQuan = 0) : copy[0].quan--;
return copy;
} else return state;
}
let store = createStore(combineReducers({ reducer, reducer2 }));
성공!
redux 쓰는 잘못된 용례
위와 같이 모달창을 열고 닫을 때는 굳이 redux로 저장할 필요는 없다.
state 데이터를 다르컴포넌트에서 쓸 일이 없다면 간단하게 useState()로 Cart컴포넌트 안에 만드는 것이 효율적이다.
반면에 많은 컴포넌트들이 공유하는 값은 redux store안에 보관하는 것이 좋다.
Detail 페이지에 장바구니 추가버튼 기능 만들기
장바구니 버튼을 눌렀을 때 state에 새로운 값을 추가하는 기능을 만들것인데
이 기능은 reducer에서 데이터 수정방법 하나 만들고 버튼누를 때 dispatch하면된다!
dispatch 할 때 데이터를 실어보내는 법이 핵심이다.
(Detail.js)
function Detail(props){
return (
<HTML많은곳/>
<button onClick={()=>{
props.dispatch({type : '항목추가', payload : {id : 2, name : '새로운상품', quan : 1} })
}}>주문하기</button>
)
}
주문하기 버튼을 누르면 dispatch 한다.
데이터들과 유사한 데이터를 실어보낼 때는 type말고 payload라는 항목을 만들어 주고 데이터를 넣어준다. (payload는 화물이라는 뜻이 있어서 자주 관습적으reducer 함수 안에서 액션.payload 라고 출력해보시면 아까 보낸 데이터로 사용)
여기서 중요한 점은 props.dispatch를 그냥 쓰면 오류가 나기 때문에 꼭 connect를 해줘야한다.
function state를props화(state){
console.log(state);
return {
state : state.reducer,
alert열렸니 : state.reducer2
}
}
export default connect(state를props화)(Detail)
(index.js)
let 기본state = [{id : 0, name : '멋진신발', quan : 2}];
function reducer(state = 기본state, 액션){
if (액션.type === '항목추가') {
let copy = [...state];
copy.push(액션.payload);
return copy;
} else if (액션.type === '수량증가'){
let copy = [...state];
copy[0].quan++;
return copy
} else if (액션.type === '수량감소'){
let copy = [...state];
copy[0].quan--;
return copy
} else {
return state
}
}
reducer 함수 안에서 액션.payload 라고 출력해보시면 아까 보낸 데이터가 들어있다.
이제 버튼을 누르면 '항목추가' 요청을 dispatch 하게되고 데이터를 실어보낸다.
reducer에선 그 데이터를 state에 추가해주면 항목이 하나 더 생기게 된다.
Cart 페이지 방문하면 추가한 데이터가 안보이는 이유
사이트 새로 들어올 때마다 초기화가 되기 때문이다.
따라서 주문하기 버튼을 눌렀을 때 history.push() 등의 라우터 함수를 이용해서 페이지 이동을 강제로 시켜보면 초기화되지 않는다.
(Detail.js)
function Detail(props){
let history = useHistory();
return (
<HTML많은곳/>
<button onClick={()=>{
props.dispatch({type : '항목추가', payload : {id : 2, name : '새로운상품', quan : 1} });
history.push('/cart');
}}>주문하기</button>
)
}
PUSH 오류?
강의를 보면 주문하기 버튼을 누를 때 마다 push로 console.log에 reducer에 계속 들어가는것을 볼 수 있었는데, 똑같이 따라해도 정확히 원하는 값은 잘 들어가지만 중복으로 여러개씩 들어가지 않는다.
두시간째 삽질중 ,, 일단 게시판에 문의를 해봤다 ㅜㅜ

하루가 지나서 바로 답장이 오셨다..!

헉? 확인 해 보니 내가 짰던 코드들은 이러했다.
let defaltState = [
{ id: 0, name: "멋진스티커", quan: 2 },
{ id: 1, name: "예쁜스티커", quan: 4 },
];
function reducer(state = defaltState, action) {
if (action.type === "장바구니담기") {
let copy = [...defaltState];
copy.push(action.payload);
return copy;
} else if (action.type === "수량증가") {
let copy = [...defaltState];
copy[0].quan++;
return copy;
} else if (action.type === "수량감소") {
let copy = [...defaltState];
let copyQuan = copy[0].quan;
0 >= copyQuan ? (copyQuan = 0) : copy[0].quan--;
return copy;
} else {
return state;
}
}
reducer안에 state를 defaltState로 바꿨는데, defaltState를 복사해서 썼다니..
이런 실수를 하다니 :—(
수정하니 push가 잘 들어간다!
let defaltState = [
{ id: 0, name: "멋진스티커", quan: 2 },
{ id: 1, name: "예쁜스티커", quan: 4 },
];
function reducer(state = defaltState, action) {
if (action.type === "장바구니담기") {
let copy = [...state];
copy.push(action.payload);
return copy;
} else if (action.type === "수량증가") {
let copy = [...state];
copy[0].quan++;
return copy;
} else if (action.type === "수량감소") {
let copy = [...state];
let copyQuan = copy[0].quan;
0 >= copyQuan ? (copyQuan = 0) : copy[0].quan--;
return copy;
} else {
return state;
}
}
앞으로 이런 실수는 절대 하지 않을 것 같다!
이전에는 state를props화 해주는 함수를 파일 밑에 첨부하면 된다고 배웠다. 하지만 코드가 길어지도 복잡해서 어려운 단점이 있었다.
이를 해결하기 위해 useSelector Hook을 쓰묜 state를 조금 더 쉽게 꺼내올 수 있다.
useSelector
(이전 방법)
(Cart.js 하단)
function state를props화(state){
return {
state : state.reducer,
alert열렸니 : state.reducer2
}
}
export default connect(state를props화)(Cart)
(이후 방법)
(Cart.js 하단)
export default Cart;
이전에 길게 짜야했던 코드를 지우고 export default Cart; 만 남겨놓는다.
import { useSelector } from 'react-redux';
function Cart(props) {
let state = useSelector((state) => state )
console.log(state.reducer)
(생략)
}
- useSelector() 라는 함수를 import
- useSelector() 안에 콜백함수
이때 저장된 변수를 콘솔창에 출력해보시면 진짜로 redux state가 나온다.
<tbody>
{
state.reducer.map((a,i)=>{
return (
<tr key={i}>
<td>{ a.id }</td>
<td>{ a.name }</td>
<td>{ a.quan }</td>
useSelector로 가져온 state는 이와 같이 사용할 수있다.
useDispatch
dispatch() 간단하게 가져오고 싶으면 useDispatch를 사용하면 된다.
import { useSelector, useDispatch } from 'react-redux';
function Cart(props) {
let state = useSelector((state) => state )
let dispatch = useDispatch()
(생략)
}
props.dispatch()로 state 수정요청 보내 던 방법 대신 dispatch() 만으로 간단하게 사용할 수 있다.
밑에서 props.dispatch() 하던 코드들을 dispatch()
<button
className="btn btn-danger"
onClick={() => {
dispatch()({
type: "장바구니담기",
payload: {
id: props.sticker[id].id,
name: props.sticker[realID].title,
quan: 1,
},
});
history.push("/cart");
}}
>
Order
</button>
useSelector, useDispatch hook을 이용하여 redux store에 있던 데이터를 쉽게 활용할 수 있다!
직접 해볼 응용
- 지금은 주문하기 누르면 임시상품명을 state에 추가하고 있는데 현재 페이지에 있는 상품명을 추가하기
- 주문하기 버튼을 눌렀을 때 같은 상품이 이미 state에 존재하면 수량만 증가시켜주게 하고 싶으면?
- 새로 추가한 상품은 +1 안되는 버그 해결은?
'Front-end > React' 카테고리의 다른 글
| [React] 코딩애플 blog-part3-6 (리액트에서 자주쓰는 if문 작성패턴 5개) (0) | 2022.01.09 |
|---|---|
| [React] 코딩애플 blog-part3-4 (장바구니 기능 완성) (0) | 2022.01.09 |
| [React] 코딩애플 blog-part3-3 (Redux 설치 및 셋팅/사용) (0) | 2022.01.01 |
| [React] 코딩애플 blog-part3-2 (애니메이션 ) (0) | 2022.01.01 |
| [React] 코딩애플 blog-part3-1 (Context API) (0) | 2022.01.01 |