useReducer

쏘뽀끼·2024년 7월 20일

react

목록 보기
2/25

코드를 따라서 코딩을 하다가 useReducer에 대한 사용방법 인지가 아직 부족함을 깨닫고

개념에 대해 공부해보고자 포스팅을 시작합니다.. ㅎ



컴포넌트에 state를 생성하고 관리하기 위해서 주로 useState 훅을 사용했다.

그러나 react에서 state 관리를 위해 useReducer 훅으로도 사용할 수 있다고 한다.

useReducer는 state처럼 state를 생성, 관리할 수 있는 도구이다.



사용할 때?

  • 여러개의 하위 값을 포함하는 복잡한 state를 다뤄야 할 때 useState대신 useReducer를 사용한다.
 {
 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을 담아서 은행이라는 Redcuer에게 보냄으로써 State를 변경할 수 있다.

Dispatch(요구) ----> Action('만원을 출금해주세요.')------> Reducer(은행)에게 전달 -----> 거래내역이 업데이트(State)가 된다.



철수는 은행이라는 Reducer에게 다른 Action들을 보냄으로써 예금, 출금, 송금 등 복잡한 일을 할 수 있다.

복잡한 일을 하는 것에 있어서 Reducer는 아주 최고인 것!

철수의 Action에 담겨있는 내용대로 알아서 처리해주기 때문



즉 이러한 패턴을 Component의 관점으로 보면,

Dispatch(Action) ------> Reducer(State, Action) --- Update => State가 업데이트!
State를 업데이트 시켜주기 위해서는 Dispatch라는 함수의 인자로 Action을 넣어서 Reducer에게 전달해주는 것이다.



즉 Reducer가 state를 우리의 Action 안에 들어가 있는 내용대로 업데이트 시켜주는 것이다.

인풋 안에 있는 값 만큼 예금을 넣으면 잔고가 늘어나고 출금을 누르면 그만큼 잔고가 줄어든다.

이렇게 만드려면

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원 이렇게 잘 나오는 걸 볼 수 있다.

  • useRedcuer를 통해서 money라는 state생성

-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;
 

0개의 댓글