React (useReducer)

Jeonghun·2023년 5월 25일
7

React

목록 보기
8/21


React의 또 다른 상태관리 hook, useReducer

이번 포스팅에서는 useState를 이은 React의 상태관리 hook 중 하나인 'useReducer'에 대해 알아보자.

- useReducer는 무엇인가?

🤔 useReducer, 넌 또 뭐냐?
useReducer는 React에서 제공하는 훅 중 하나로, 상태 업데이트 로직을 컴포넌트 외부로 분리할 수 있도록 도와준다. 또한 useState보다 더 복잡한 컴포넌트 상태를 관리하는 데에 유용하다. 따라서 useState의 상위호환이라고 볼 수 있겠다.

useReducer는 기본적으로 아래와 같은 형태를 가진다.

const [state, dispatch] = useReducer(reducer, initialArg, init);
  1. useReducer는 두 개의 값을 배열 형태로 반환하는데, 그 값은 'state'와 'dispatch' 함수이다.
  • state: 이 값은 우리가 관리하고자 하는 현재 상태이다. useReducer에서는 reducer 함수를 통해 상태를 업데이트하게 된다. 초기 상태는 useReducer의 두 번째 인자로 주어진 initialArg가 되며, dispatch를 통해 전달된 action에 따라 reducer 함수가 새로운 상태를 생성하고, 이 새로운 상태는 state에 반영된다.

  • dispatch: 이 함수는 reducer 함수에 action을 전달하는 역할을 한다. dispatch 함수에는 액션 객체를 인자로 전달하게 되며, 이 액션 객체는 reducer 함수의 두 번째 인자로 전달된다. dispatch를 호출함으로써 reducer가 실행되고, 새로운 상태가 생성되어 state에 반영된다.
    dispatch를 통해 전달되는 액션 객체는 주로 {type: 'ACTION_TYPE', ...}와 같은 형태를 가진다. 여기서 type은 어떤 액션을 실행할지를 결정하는 필드이며, 나머지 필드들은 액션에 따른 추가적인 데이터를 담당한다.

  1. useReducer는 다음 세 가지의 인자를 받는다.
  • reducer: 현재의 상태와 액션 객체를 받아 다음 상태를 반환하는 함수
  • initialArg: 초기 상태 값
  • init: 초기화 함수로, 초기 상태를 생성하는 데 사용되며, 선택적 매개변수이다.

📌 useReducer 사용 예시

import React, { useReducer } from 'react'; // React와 useReducer import

// 초기 상태값 정의
const initialState = { count: 0 };

// reducer 함수 정의
// 이 함수는 현재 상태와 액션 객체를 인자로 받아 새로운 상태를 반환한다.
function reducer(state, action) {
  // 액션 타입에 따라 다른 작업을 수행한다.
  switch (action.type) {
    case 'increment':
      // increment 액션의 경우, count를 1 증가
      return { count: state.count + 1 };
    case 'decrement':
      // decrement 액션의 경우, count를 1 감소
      return { count: state.count - 1 };
    default:
      // 알 수 없는 액션 타입이 주어졌을 때 에러 발생
      throw new Error();
  }
}

// Counter 컴포넌트
function Counter() {
  // useReducer 호출
  // 첫 번째 인자는 reducer 함수, 두 번째 인자는 초기 상태 값
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      Count: {state.count} {/* 현재 카운트 값 출력 */}
      {/* 각 버튼 클릭 시 dispatch 함수를 호출하여 액션 발생 */}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

위 코드에서는 useReducer 훅을 사용하여 상태를 관리하는 Counter 컴포넌트를 구현했다. 각 버튼의 onClick 이벤트에 dispatch 함수를 연결하여 'increment'와 'decrement' 액션을 발생시키며, 이 액션들은 reducer 함수로 전달되어 현재 상태를 업데이트 하게된다.

- 조금 더 복잡하게, useReducer와 useEffect

useEffect와 useReducer를 함께 사용하면 외부 데이터를 불러와 상태를 관리하는 등의 복잡한 작업을 수행할 수 있다. 예를 들어, API를 호출하여 데이터를 불러오고, 이를 useReducer를 이용해 상태로 관리하는 경우를 생각해보자.

📌 useReducer + useEffect

import React, { useReducer, useEffect } from 'react';
import axios from 'axios'; // HTTP 요청을 위한 axios 사용

// 초기 상태 정의, 이 예제에서는 비동기 데이터 로딩 상태를 관리한다.
const initialState = { loading: true, data: null, error: null };

// reducer 함수 정의
function reducer(state, action) {
  // 액션의 type에 따라 다른 작업을 수행한다.
  switch (action.type) {
    case 'SUCCESS':
      // 'SUCCESS' 액션의 경우, 로딩 상태를 해제하고, 받아온 데이터를 저장한다.
      return { loading: false, data: action.data, error: null };
    case 'ERROR':
      // 'ERROR' 액션의 경우, 로딩 상태를 해제하고, 에러 메시지를 저장한다.
      return { loading: false, data: null, error: action.error };
    default:
      // 알 수 없는 액션 타입이 주어졌을 때 에러를 발생한다.
      throw new Error();
  }
}

function DataFetching() {
  // useReducer 호출
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    // fetchData 함수 정의 및 호출
    // axios를 사용하여 비동기 API 요청을 보냄
    const fetchData = async () => {
      try {
        const response = await axios.get('https://api.example.com/data');
        // 요청이 성공하면, 'SUCCESS' 액션을 dispatch
        dispatch({ type: 'SUCCESS', data: response.data });
      } catch (e) {
        // 요청이 실패하면, 'ERROR' 액션을 dispatch
        dispatch({ type: 'ERROR', error: e });
      }
    };
    fetchData();
  }, []); // 빈 종속성 배열을 사용하여 컴포넌트 마운트 시에만 fetchData를 호출

  // state에서 필요한 값 추출 (객체 구조 분해)
  const { loading, data, error } = state;

  // 로딩, 에러, 데이터 상태에 따라 다른 UI를 렌더링
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
  return <div>{data && data.map(item => <div key={item.id}>{item.name}</div>)}</div>;
}

위 예제 코드에서는 useReducer와 useEffect를 함께 사용하여 비동기 데이터 로딩 상태를 관리하는 예제를 구현한다. useEffect 내부에서 정의된 fetchData 함수는 API 요청을 보내고, 그 결과에 따라 'SUCCESS' 혹은 'ERROR' 액션을 dispatch 하게된다. 이 액션들은 reducer에 의해 처리되어 상태가 업데이트 된다.

- useReducer와 useState의 차이점

🤔 똑같은 상태관리면 useState와 useReducer 둘 중 뭐가 더 좋지?
useState와 useReducer는 React에서 상태를 관리하기 위한 Hook이다. 간단한 상태 관리의 경우 useState를, 복잡한 상태 관리나 상태 업데이트 로직을 컴포넌트 외부로 분리하고 싶은 경우 useReducer를 사용하는 것이 일반적이다.

useState는 단일 상태 값을 관리하는 반면, useReducer는 다양한 상태를 하나의 객체로 관리할 수 있어 복잡한 상태 관리에 더 유용하다. 또한 useReducer는 상태를 변경하는 로직을 별도의 reducer 함수로 관리하므로 테스트가 용이하고 코드의 재사용성이 높아진다.

상황에 맞는 hooks를 적절하게 잘 사용할 수 있도록 많은 공부가 필요할 것 같다.


profile
안녕하세요, 프론트엔드 개발자 임정훈입니다.

0개의 댓글