0209

FE

[React] useReducer란 무엇인가요? 상태 관리의 또 다른 방법!

React에서 상태를 관리할 때 대부분 useState로 시작한다. 간단한 상태라면 이걸로 충분할 수 있다. 예를 들어 숫자 카운트를 증가시키거나 입력 필드를 제어할 때 말이다.

하지만 앱이 커지고 상태가 서로 얽히고설키는 상황이 오면 얘기가 달라진다.

예를 들어 이런 질문이 떠오르게 된다.

  • 버튼 하나를 눌렀을 때 어떤 상태는 바뀌고 어떤 상태는 유지돼야 할까?

  • 여러 조건(if/switch)마다 상태를 바꾸려면 코드가 너무 복잡한데?

  • 이 상태 업데이트... 나중에 유지보수하기 어렵지 않을까?

이럴 때 등장하는 게 바로 useReducer이다!

useReducer란?

useReducer는 상태(state)업데이트 로직(dispatch + reducer)를 분리해서 관리할 수 있도록 도와주는 React의 Hook이다.

Redux와 매우 유사한 방식으로 동작하며, 복잡한 상태 전환 로직을 명확하게 분리할 수 있다는 장점이 있다.

JavaScript
// 사용 방법
const [state, dispatch] = useReducer(reducer, initialState);

구성 요소

설명

state

현재 상태값

dispatch

상태를 업데이트하기 위한 함수

reducer

액션에 따라 상태를 변경하는 로직

initialState

초기 상태값

여기서 중요한 건, 상태(state)를 직접 바꾸지 않고, 액션(action)을 통해 간접적으로 바꾼다는 점이다.

useReducer는 언제 쓰면 좋을까?

  • 상태의 전환 로직이 복잡할 때 (예 : if나 switch문이 많을 때)

  • 상태가 여러 개로 나뉘고 서로 의존성이 있을 때

  • 상태 업데이트를 한 곳에 집중시키고 싶을 때

  • Redux 스타일로 상태를 관리하고 싶을 때

이러한 상황 속에서 useReducer로 구조를 잡게 되면 상태 관리에 도움이 될 것이다.

예제: 할 일(ToDo) 리스트 만들기

예제를 통해서 useReducer를 제대로 이해해보자. 이를 위해 할 일 리스트를 만드는 예제를 가져왔다.

1) 기능 요약

code
? 추가: input에 값을 입력하고 버튼 클릭 → 리스트에 추가
? 토글: 완료 / 미완료 상태 토글
? 삭제: 항목 삭제

2) 코드

JavaScript
import React, { useReducer, useState } from 'react';

// 1. 액션 타입 모음
const ACTIONS = {
  ADD: 'add-todo',
  TOGGLE: 'toggle-todo',
  DELETE: 'delete-todo',
};

// 2. 리듀서 함수
function reducer(todos, action) {
  switch (action.type) {
    case ACTIONS.ADD:
      return [
        ...todos,
        { id: Date.now(), name: action.payload.name, complete: false },
      ];
    case ACTIONS.TOGGLE:
      return todos.map((todo) =>
        todo.id === action.payload.id
          ? { ...todo, complete: !todo.complete }
          : todo
      );
    case ACTIONS.DELETE:
      return todos.filter((todo) => todo.id !== action.payload.id);
    default:
      return todos;
  }
}

// 3. 메인 컴포넌트
export default function TodoApp() {
  const [todos, dispatch] = useReducer(reducer, []);
  const [input, setInput] = useState('');

  const handleAdd = () => {
    if (input.trim() === '') return;
    dispatch({ type: ACTIONS.ADD, payload: { name: input } });
    setInput('');
  };

  return (
    <div style={{ padding: 20 }}>
      <h2>? ToDo 리스트</h2>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="할 일을 입력하세요"
      />
      <button onClick={handleAdd}>추가</button>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id} style={{ marginTop: 10 }}>
            <span
              style={{
                textDecoration: todo.complete ? 'line-through' : 'none',
                marginRight: 10,
              }}
            >
              {todo.name}
            </span>
            <button
              onClick={() =>
                dispatch({ type: ACTIONS.TOGGLE, payload: { id: todo.id } })
              }
            >
              {todo.complete ? '되돌리기' : '완료'}
            </button>
            <button
              onClick={() =>
                dispatch({ type: ACTIONS.DELETE, payload: { id: todo.id } })
              }
            >
              삭제
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

3) 코드 설명

(1) reducer 함수

JavaScript
function reducer(state, action) {
  switch (action.type) {
    ...
  }
}
  • reducer는 현재 상태와 액션을 받아서 새로운 상태를 반환하는 순수 함수이다.

  • 어떤 액션을 보냈는지에 따라 상태를 바꿔준다.

  • reducer는 상태를 직접 바꾸지 않고, 기존 상태를 복사해서 새로운 상태를 리턴한다.

(2) dispatch

JavaScript
dispatch({ type: ACTIONS.ADD, payload: { name: input } });
  • dispatch는 상태를 바꾸고 싶다는 요청(액션)을 보내는 함수입니다.

  • 실제로 상태를 바꾸는 건 reducer입니다.

  • 상태를 직접 변경하지 않고, dispatch({ type, payload })를 통해 요청만 보낸다.

  • type은 어떤 동작인지 명시, payload는 필요한 데이터 전달

요약

개념

의미

useState

간단한 상태 관리에 적합

useReducer

복잡한 상태 전환과 구조적인 관리에 적합

reducer

상태를 어떻게 바꿀지를 정의하는 함수

dispatch

상태를 바꿔달라고 요청하는 트리거 함수

마무리

상태 관리가 복잡해질수록, 코드가 점점 지저분해지고 버그도 늘어나게 된다.

이럴 때 useReducer는 명확한 흐름과 구조를 만들어주는 훌륭한 도구가 되어줄 것이다.