코드를 따라서 코딩을 하다가 useReducer에 대한 사용방법 인지가 아직 부족함을 깨닫고
개념에 대해 공부해보고자 포스팅을 시작합니다.. ㅎ
컴포넌트에 state를 생성하고 관리하기 위해서 주로 useState 훅을 사용했다.
그러나 react에서 state 관리를 위해 useReducer 훅으로도 사용할 수 있다고 한다.
useReducer는 state처럼 state를 생성, 관리할 수 있는 도구이다.
사용할 때?
{
teacher: 'James',
students: ['Kim', 'Ann', 'Jhon'],
count:3,
location: [
{country: 'Korea', name: 'A'},
{country: 'Australia', name: 'B'},
]
}
useReducer로 코드를 훨씬 더 깔끔하게 쓸 수 있으며, 유지보수도 편해진다는 장점이 있다.
사용전 알아야 할 3 가지
-Reducer
-Dispatch
-Action
useReducer은 이 세 가지로 이루어져 있다.
비유로 예를 들어 보자.
철수가 은행에 방문했다고 하자.
철수가 은행에서 "만원을 출금해주세요."라고 요구하면 => 철수의 계좌 거래내역에서 만원이 빠져나갔다고 기록된다.
중요한 포인트는 철수가 직접적으로 거래내역을 업데이트 시키지 않는다는 점!
거래내역을 업데이트 시키는 건 철수 대신 은행이 해주는 것!
철수가 은행에게 요구 -> 은행이 요구에 따라 내역을 업데이트
여기서 거래내역이 = State
거래내역 State는 철수가 직접 수정하지 않고 은행이 철수의 요구대로 대신 업데이트 해 준다.
여기서 은행이 = Reducer라고 할 수 있다.
즉 Reducer는 state를 업데이트 해주는 역할을 한다.
컴포넌트의 state를 변경하고 싶다면 꼭 Reducer를 통해서 실행해야 한다.
철수는 거래내역이라는 state를 변경하기 위해 은행(Reducer)에게 요구를 했다.
이 요구가 = Dispatch가 된다.
철수의 요구 : '만원을 출금해주세요." 라는 내용이 담겨 있다. 이 내용이 = Action
Dispatch(요구) ----> Action('만원을 출금해주세요.')------> Reducer(은행)에게 전달 -----> 거래내역이 업데이트(State)가 된다.
철수는 은행이라는 Reducer에게 다른 Action들을 보냄으로써 예금, 출금, 송금 등 복잡한 일을 할 수 있다.
복잡한 일을 하는 것에 있어서 Reducer는 아주 최고인 것!
철수의 Action에 담겨있는 내용대로 알아서 처리해주기 때문
즉 이러한 패턴을 Component의 관점으로 보면,
Dispatch(Action) ------> Reducer(State, Action) --- Update => State가 업데이트!
State를 업데이트 시켜주기 위해서는 Dispatch라는 함수의 인자로 Action을 넣어서 Reducer에게 전달해주는 것이다.
인풋 안에 있는 값 만큼 예금을 넣으면 잔고가 늘어나고 출금을 누르면 그만큼 잔고가 줄어든다.
이렇게 만드려면
import React, {useState} from 'react';
function App(){
const[number, setNumber]=useState(0);
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: ?원</p>
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000" //인풋 안의 숫자가 1000단위로 올라가고 내려갈 수 있도록 스텝을 1000으로 설정
/>
<button>예금</button>
<button>출금</button>
</div>
);
}
일단 위의 상태는 예금 버튼을 눌렀을 때 인풋 안의 값이 잔고에 추가되어야 하고 출금 버튼을 누르면 그만큼 잔고가 줄어들도록 Reducer를 활용하여 만들어보자.
import React, {useState, useReducer} from 'react'; //1.useReducer impport로 가져오기
//3. 컴포넌트 밖에 redcuer라는 함수 만들기
const reducer (state, action) => {}//reducer함수는 두 가지를 인자로 받는다.
//첫 번째 인자로 현재 state를 받는다.
//두 번째 인사로 Action을 받는다.
function App(){
const[number, setNumber]=useState(0);
const[money, dispatch] =useReducer(reducer,0);//2.새로운 state생성, state와 비슷하게 배열을 하나 반환해 준다.
//배열의 첫 번째 요소에는 새로 만들어진 State가 들어있다. 여기서는 money로 이름을 지어줌.
//두 번째 요소에는 useReducer가 만들어준 dispatch함수가 들어있다.
//useReducer는 인자로 두 가지를 받는다. 첫 번째: Reducer, 두 번째는 money state안에 들어갈 초기 값을 받는다.
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>//4. money넣어주기
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000" //인풋 안의 숫자가 1000단위로 올라가고 내려갈 수 있도록 스텝을 1000으로 설정
/>
<button>예금</button>
<button>출금</button>
</div>
);
}
그럼 화면에 잔고가 :0원 이렇게 잘 나오는 걸 볼 수 있다.
-money State는 잔고에 지금 얼마가 있는 지를 나타내는 기록
-money State는 우리가 만든 reducer를 통해서만 수정이 된다.
-reducer는 useReducer의 인자로 전달되었다.
*reducer를 통해서 money State를 수정하고 싶을 때마다 dispatch를 불러 줄 것이다.
-dispatch는 useReducer가 만들어준 함수로 사용할 때 인자로 Action을 넣어줄 것이다.
-dispatch를 부르면 reducer가 호출이 된다. reducer의 인자로 action이 전달된다.
-그럼 action을 토대로 redcuer는 state를 변경한다.
예금 버튼이 눌릴 때마다 dispatch를 불러내보자.
import React, {useState, useReducer} from 'react';
//3. 컴포넌트 밖에 redcuer라는 함수 만들기
const reducer (state, action) => {
console.log('redcuer가 일을 합니다.');//확안하기 위해 console.log적기
}
function App(){
const[number, setNumber]=useState(0);
const[money, dispatch] =useReducer(reducer,0);
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>//4. money넣어주기
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000"
/>
<button onClick={()=>
dispatch();
}>예금</button>// onClick 추가해주기, 콜백함수로 dispatch부르기 = reducer가 자동으로 불리게 된다.
<button>출금</button>
</div>
);
}
잘 나오는 걸 볼 수 있다.
그럼 각각 state와 action에 뭐가 들어있는지 확인해보자 .
import React, {useState, useReducer} from 'react';
//3. 컴포넌트 밖에 redcuer라는 함수 만들기
const reducer (state, action) => {
console.log('redcuer가 일을 합니다.', state, action);//state action 넣어주기
}
function App(){
const[number, setNumber]=useState(0);
const[money, dispatch] =useReducer(reducer,0);
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000"
/>
<button onClick={()=>
dispatch();
}>예금</button>
<button>출금</button>
</div>
);
}
state는 0, action에는 undefined가 나온걸 볼 수 있다.
money State를 초기값으로 0을 지정해줬기 때문이다.
-action이 undefined인 건 dispatch의 인자로 아무것도 전달해주지 않았기 때문이다.
한번 action을 전달해보자.
import React, {useState, useReducer} from 'react';
//3. 컴포넌트 밖에 redcuer라는 함수 만들기
const reducer (state, action) => {
console.log('redcuer가 일을 합니다.', state, action);
}
function App(){
const[number, setNumber]=useState(0);
const[money, dispatch] =useReducer(reducer,0);
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000"
/>
<button onClick={()=>
dispatch({type: "deposit"});//action은 보통 object형태로 넣는다. object안에 type을 넣는다. type은 reducer에게 요구하고 싶은 내용을 집어넣으면 된다.
}>예금</button>
<button>출금</button>
</div>
);
}
이렇게 하면
잘 들어온 걸 확인할 수 있다.
그러나 목표는 인풋에 들어간 숫자 만큼 money state를 증가시켜서 잔고에 보여주고 싶었다.
그러기 위해서는 action안에(dispatch 인자로)한 가지를 더 추가할 것이다.
바로 payload이다.
import React, {useState, useReducer} from 'react';
//3. 컴포넌트 밖에 redcuer라는 함수 만들기
const reducer (state, action) => {
console.log('redcuer가 일을 합니다.', state, action);
}
function App(){
const[number, setNumber]=useState(0);
const[money, dispatch] =useReducer(reducer,0);
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000"
/>
<button onClick={()=>
dispatch({type: "deposit", payload:number});//payload안에 현재 input에 들어있는 값을 넣어준다.
}>예금</button>
<button>출금</button>
</div>
);
}
payload에 3000이 들어가는 걸 확인할 수 있다.
이제 reducer가 action을 잘 받으니 내용대로 state를 업데이트 시켜줘야 한다.
reducer가 return하는 값이 새로 업데이트된 state가 되는 것이다.
import React, {useState, useReducer} from 'react';
//3. 컴포넌트 밖에 redcuer라는 함수 만들기
const reducer (state, action) => {
console.log('redcuer가 일을 합니다.', state, action);
return state + action.payload; //현재 있는 state(0) + action의 payload(인풋 안의 값)값을 더하면 된다.
}
function App(){
const[number, setNumber]=useState(0);
const[money, dispatch] =useReducer(reducer,0);
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000"
/>
<button onClick={()=>
dispatch({type: "deposit", payload:number});
}>예금</button>
<button>출금</button>
</div>
);
}
이제 잔고의 값이 바뀐 걸 확인할 수 있다.
useReducer도 useState와 똑같이 state가 바뀔 때마다 component를 렌더링 해준다.
그렇기 때문에 화면이 다시 rendering이 돼서 새로운 state를 보여줄 수 있는 것이다.
그러나! 우리는 예금 뿐만 아니라 출금도 해야 한다.
즉 reducer가 항상 state에 payload를 더한 값이면 안된다.
action안에 들어있는 type에 따라서 다르게 state를 업데이트 시켜줘야 한다.
그래서 보통 reduce안에 if else 문을 쓰거나 switch문을 쓴다.
switch문으로 사용해보겠다.
import React, {useState, useReducer} from 'react';
//3. 컴포넌트 밖에 redcuer라는 함수 만들기
const reducer (state, action) => {
console.log('redcuer가 일을 합니다.', state, action);
switch(action.type){
case'deposit': // type이 deposito이라면
return state + action.payload;// state에 payload더하기
case 'withdraw';// type추가
return state - action.payload;
default:
return state //default값은 현재 있는 값으로 하기
}
};
function App(){
const[number, setNumber]=useState(0);
const[money, dispatch] =useReducer(reducer,0);
return(
<div>
<h2> useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type='number'
value={number}
onChange={(e)=> setNumber(parseInt(e.target.value))}
step="1000"
/>
<button onClick={()=>
dispatch({type: "deposit", payload:number});
}>예금</button>
<button onClic={()=>
dispatch({type: "withdraw", payload:number});//출금 action과 type지정하기
}>출금</button>
</div>
);
}
출금, 예금 둘다 잘 되는 걸 확인 할 수 있다.
만약 코드를 더 깔끔하게 하고 싶다면 switch문의 type들을 const로 빼주면 된다.
import React, { useState, useReducer } from 'react';
const ACTION_TYPES = {
deposit: 'deposit',
withdraw: 'withdraw',
};
const reducer = (state, action) => {
console.log('reducer가 일을 합니다.', state, action);
switch (action.type) {
case ACTION_TYPES.deposit:
return state + action.payload;
case ACTION_TYPES.withdraw:
return state - action.payload;
default:
return state;
}
};
function App() {
const [number, setNumber] = useState(0);
const [money, dispatch] = useReducer(reducer, 0);
return (
<div>
<h2>useReducer 은행에 오신 것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
step="1000"
/>
<button onClick={() => dispatch({ type: ACTION_TYPES.deposit, payload: number })}>
예금
</button>
<button onClick={() => dispatch({ type: ACTION_TYPES.withdraw, payload: number })}>
출금
</button>
</div>
);
}
export default App;