[React] useState vs useReducer

김재훈·2023년 4월 21일
0

상태관리

React는 DOM을 직접 조작하는 것이 아니라 JSX 문법으로 이루어진 코드로 가상 DOM을 생성하여 실제 DOM에 변경 사항을 반영한다. 이렇게 함으로써 불필요한 렌더링을 줄일 수 있는데 이 가상 DOM을 만들 때 상태 값 변화에 따라 컴포넌트가 재구성되고 리렌더링이 이루어진다.

그래서 상태 관리는 매우 중요하며 hook과 라이브러리를 통해서 다양하게 관리할 수 있다. 많은 상태관리 방법 중 useState와 useReducer가 있다.

useState

  • state를 관리해주는 함수
  • 상태의 기본값을 인수로 넘겨준다
  • 해당 함수를 호출하면 배열이 반환된다. (1번째 요소는 현재 상태, 2번째 요소는 setter 함수)
  • setter 함수는 인수로 전달 받은 값을 state 값으로 설정한다. (state가 업데이트 될 때마다 리렌더링 된다.)

아래 예제는 useState를 사용하여 장바구니 물품 개수를 관리한다.
1. 버튼 클릭시 setter 함수를 호출하여 상태값을 변경한다.
2. 상태값 변경으로 화면이 리렌더링된다.

const [cart, setCart] = useState(0);
const onAddHandler = () => {
  alert('10개 이상은 추가할 수 없습니다.');
  return;
}

setCart(prevCart => prevCart + 1);

return (
  <div>
    <div>장바구니 물품 {cart}</div>
    <button onClick={onAddHandler}>추가</button>
  </div>
);

useReducer

  • useState보다 좀 더 복잡한 상태 관리가 필요한 경우에 사용한다.
  • reducer 함수, 초기 상태값, 초기 함수 인수를 받는다.
  • 해당 함수를 호출하면 배열이 반환된다. (1번째 요소는 현재 상태, 2번째 요소는 dispatch 함수)

1) reducer

  • 현재 state와 action을 두 개의 인수로 받아 새로운 상태를 반환하는 함수
  • dispatch가 전달한 액션 객체에 따라 상태를 업데이트
  • (state, action) => newState 형태

2) 초기 상태값

처음에 설정해주는 값

3) 초기 함수

초기 state를 지연해서 생성

4) dispatch

  • 컴포넌트 내의 상태를 업데이트하기 위해 사용
  • reducer의 액션 객체를 넘긴다.

아래 예제는 useReducer를 사용하여 장바구니 물품 개수를 관리한다.
1. 버튼 클릭 시 dispatch 함수를 호출하여 타입이 ADDCART인 action 객체 전달을 한다.
2. reducer 함수가 action 객체를 전달받아 타입이 같은 케이스를 찾아 상태 변경 후 반환한다.
3. 상태가 변경되고 리렌더링이 된다.

// 초기값
const initialState = { cart: 0 };

// reducer 함수
const reducer = (state, action) => {
  switch(action.type) {
    case 'ADDCART':
      if(cart > 10) {
        throw new Error('10개 이상은 추가할 수 없습니다.');
      }
      
      return { cart: state.cart + 1 };
  }
};

// App 컴포넌트
const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const onAddHandler = () => dispatch({ type: 'ADDCART' });
  
  return (
    <div>
      <div>장바구니 물품 {state.cart}</div>
      <button onClick={onAddHandler}>추가</button>
   </div>
  )
}

useState vs useReducer

위의 동일한 로직을 useState와 useReducer로 작성한 것을 보면 간단한 로직이기 때문에 어떻게 보면 useState로만 작성해도 된다.

하지만 onAddHandler 함수를 보면 useReducer를 사용하여 작성한 코드가 useState로 작성한 코드보다 간결하다. 그리고 type을 통해 어떤 기능을 구현하고 있는지 나타내 가독성 측면에서도 좋다.

/// useState를 사용한 경우
const onAddHandler = () => {
  if(cart > 10) {
    throw new Error('10개 이상은 추가할 수 없습니다.');
  }
  
  setCart(prevCart => prevCart + 1);
}

// useReducer를 사용한 경우
const onAddHandler = () => dispatch({ type: 'ADDCART' });

위와 같은 비즈니스 로직이 한두 개가 아니고 점점 많아질 가능성이 있다면 아래와 같이 useReducer를 사용하게 되면 일관성 있는 패턴으로 코드를 작성할 수 있다.

const OnAddHandler = () => dispatch({ type: 'ADDCART' });
const OnDeleteHandler = () => dispatch({ type: 'DELETECART' });

비즈니스 로직이 훨씬 더 길어지고 프로젝트의 규모가 클 경우 useReducer를 사용하면 간결하고 어떤 로직을 실행하고 있는지 한눈에 파악할 수 있으며 일관성 있게 코드를 작성할 수 있다.

참고

https://prod.velog.io/@dee0518/React-useReducer#-%EC%98%A4%EB%8A%98%EC%9D%98-%EA%B3%B5%EB%B6%80-%EC%9D%BC%EA%B8%B0

profile
김재훈

0개의 댓글