리액트 강좌: useReducer 이해하기

React는 구성 요소를 기반으로 한 JavaScript 라이브러리로, 사용자 인터페이스(UI) 구축을 위해 널리 사용됩니다. React의 두 가지 주요 상태 관리 훅은 useState와 useReducer입니다. 이번 강좌에서는 useReducer에 대해 깊이 있게 살펴보겠습니다.

1. useReducer란?

useReducer는 리액트의 내장 훅 중 하나로, 복잡한 상태 로직을 관리하기 위해 사용됩니다. 이 훅은 상태 변화가 있을 때마다 특정한 함수를 호출하여 상태를 업데이트합니다. useState가 간단한 상태 관리를 위한 훅이라면, useReducer는 보다 복잡한 로직이나 다수의 상태 변화를 필요로 할 때 적합합니다.

2. useReducer 기본 사용법

useReducer를 사용하는 기본적인 방법은 다음과 같습니다.


import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

위의 예제에서 볼 수 있듯이 useReducer는 두 개의 인자를 사용합니다:
reducer 함수초기 상태(initial state). reducer 함수는 현재 상태와 액션을 매개변수로 받아서 새로운 상태를 반환합니다. dispatch 함수는 액션 객체를 사용하여 상태를 업데이트합니다.

3. useReducer의 장점

  • 복잡한 상태 관리: useReducer는 여러 상태를 관리하기 위한 강력한 도구입니다. 여러 액션과 상태를 명확히 규명할 수 있습니다.
  • 리팩토링과 확장성: reducer 함수는 독립적으로 관리되기 때문에 상태 로직을 리팩토링하기가 용이합니다.
  • 테스트 용이성: reducer 함수는 순수 함수이기 때문에, 이를 단위 테스트하기가 상대적으로 수월합니다.

4. useReducer와 useState 비교

useState와 useReducer는 상태를 관리하는 데 도움을 주지만, 사용 목적에 따라 각각의 적합한 상황이 다릅니다.

특징 useState useReducer
단순한 상태 유리 불리
복잡한 상태 불리 유리
상태 변경 로직 불리 유리
상태의 분리 불리 유리

5. 실전 예제: ToDo 리스트 앱 만들기

이제 useReducer를 활용하여 간단한 ToDo 리스트 앱을 만들어 보겠습니다. 이 앱은 할 일을 추가하고, 삭제할 수 있는 기능을 포함합니다.


import React, { useReducer } from 'react';

const initialState = { todos: [] };

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return { ...state, todos: [...state.todos, action.payload] };
    case 'remove':
      return { ...state, todos: state.todos.filter((_, index) => index !== action.payload) };
    default:
      throw new Error();
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const [input, setInput] = React.useState('');

  const addTodo = () => {
    dispatch({ type: 'add', payload: input });
    setInput('');
  };

  return (
    <div>
      <h2>ToDo List</h2>
      <input 
        type="text" 
        value={input} 
        onChange={(e) => setInput(e.target.value)} 
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {state.todos.map((todo, index) => (
          <li key={index}>
            {todo} <button onClick={() => dispatch({ type: 'remove', payload: index })}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

위의 TodoApp 컴포넌트는 사용자가 할 일을 추가하고 제거할 수 있는 기능을 제공합니다. useReducer를 사용하여 상태를 관리하며, 각 액션(type)별로 할 일을 추가하거나 삭제하는 로직을 정의하고 있습니다.

6. useReducer의 고급 사용법

useReducer는 단순한 상태 관리를 넘어서, 고급 기능을 지원합니다. 다음은 그 일부입니다:

6.1 초기 상태 로딩

초기 상태를 비동기적으로 로드할 수 있는 방법입니다. 이를 위해 useEffect 훅을 사용합니다.


import React, { useReducer, useEffect } from 'react';

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('/api/todos');
      const data = await response.json();
      data.forEach(todo => dispatch({ type: 'add', payload: todo }));
    };
    fetchData();
  }, []);

  // 나머지 앱 코드...
}

6.2 중첩된 상태 관리

여러 개의 reducer를 조합하여 구조화된 상태 관리를 적용할 수 있습니다. context API와 함께 사용할 경우, 전역 상태 관리를 손쉽게 할 수 있습니다.


function mainReducer(state, action) {
  return {
    counter: counterReducer(state.counter, action),
    todos: todoReducer(state.todos, action),
  };
}

// 상태와 dispatch는 여러 개의 reducer로 제공됩니다.
const [state, dispatch] = useReducer(mainReducer, initialState);

7. 결론

useReducer는 리액트에서 복잡한 상태 관리 로직을 구축할 수 있는 강력한 도구입니다. 특히 여러 상태 변화가 필요한 경우, useReducer를 통해 코드의 가독성과 유지보수성을 높일 수 있습니다. 이번 강좌에서 우리가 배운 내용을 바탕으로 여러분만의 리액트 애플리케이션에서 useReducer를 활용해 보시기 바랍니다.

추가로 실습하고 싶은 예제나 질문이 있다면 댓글로 남겨주세요. 여러분의 리액트 개발 여정을 응원합니다!