프로그래밍/ReactJS

React 애플리케이션의 복잡한 상태 관리, Redux로 해결하기

shimdh 2025. 10. 18. 00:10
728x90

React 애플리케이션을 개발하다 보면, 앱의 규모가 커질수록 여러 컴포넌트 간 상태 공유가 필수적입니다. props drilling(상태를 여러 계층으로 전달하는 문제)이나 로컬 상태 관리의 한계로 인해 코드가 복잡해지고 유지보수가 어려워지죠. 이런 상황에서 효과적인 상태 관리는 앱의 확장성과 안정성을 좌우합니다. 오늘 이 포스트에서는 React 생태계에서 오랜 기간 사랑받아온 상태 관리 라이브러리 Redux를 깊이 파헤쳐보겠습니다. Redux가 무엇인지, 핵심 원칙과 개념은 어떻게 구성되는지, 그리고 실제로 간단한 할 일 앱을 통해 어떻게 적용되는지 단계별로 알아보겠습니다. Redux를 처음 접하는 개발자부터 리뷰를 원하는 분들까지 유용한 내용이 될 거예요!

Redux란 무엇인가요?

Redux는 JavaScript 애플리케이션(특히 React와 함께)에서 상태를 예측 가능하고 일관된 방식으로 관리하기 위한 오픈소스 라이브러리입니다. 2015년에 Dan Abramov가 Flux 아키텍처에서 영감을 받아 개발했으며, 복잡한 상태 흐름을 단일 방향 데이터 흐름(Single-directional Data Flow) 으로 제어해 상태 변경을 쉽게 추적하고 디버깅할 수 있게 합니다. 결과적으로 대규모 앱에서도 상태가 예측 가능해져 버그를 줄이고 팀 협업이 수월해집니다.

Redux의 강점은 시간 여행 디버깅(Time-Travel Debugging)미들웨어(Middleware) 지원으로, Redux DevTools를 통해 과거 상태로 되돌아가며 문제를 진단할 수 있습니다. React 외에도 Vue, Angular 등 다양한 프레임워크와 호환되지만, React와의 시너지가 가장 크죠.

Redux의 세 가지 핵심 원칙

Redux는 세 가지 간단하지만 강력한 원칙에 기반합니다. 이 원칙들이 상태 관리를 단순하고 신뢰성 있게 만듭니다.

  1. 단일 진실 공급원 (Single Source of Truth)
    전체 앱의 상태는 하나의 거대한 객체 트리 형태로 저장소(Store)라는 단일 컨테이너에 저장됩니다. 모든 컴포넌트가 이 저장소에서 상태를 참조하므로, 데이터의 일관성을 유지하고 어디서든 현재 상태를 쉽게 파악할 수 있습니다. 예를 들어, 사용자 인증 상태나 쇼핑 카트 아이템이 앱 전체에서 공유될 때 유용하죠.

  2. 상태는 읽기 전용입니다 (State is Read-Only)
    상태를 직접 수정할 수 없습니다. 변경을 유발하는 유일한 방법은 액션(Action)이라는 평범한 JavaScript 객체를 디스패치(Dispatch)하는 것입니다. 액션은 "무엇이 일어났는지(What happened?)"를 설명하며, 이 원칙 덕분에 상태 변경의 히스토리를 추적하기 쉽고 예측 가능성이 높아집니다.

  3. 변경 사항은 순수 함수로 이루어집니다 (Changes are Made with Pure Functions)
    액션에 반응해 상태를 업데이트하는 함수를 리듀서(Reducer)라고 합니다. 리듀서는 현재 상태와 액션을 입력으로 받아 새로운 상태를 반환하는 순수 함수여야 합니다. 순수 함수는 동일한 입력에 항상 동일한 출력을 내고, 외부 상태(예: API 호출, 날짜)에 의존하지 않아 테스트와 디버깅이 용이합니다.

이 원칙들을 지키면 앱이 "예측 가능"해져, "왜 이 상태가 됐지?"라는 질문에 명확한 답을 할 수 있습니다.

Redux의 핵심 개념 파헤치기

Redux를 마스터하려면 다음 개념들을 확실히 이해해야 합니다. 각 개념에 간단한 예시를 붙여 설명하겠습니다.

1. 액션 (Actions)

  • 정의: 앱에서 저장소로 데이터를 전달하는 "정보 페이로드"입니다. 사용자 이벤트(클릭, 입력)나 비동기 작업(데이터 로드)을 설명합니다.

  • 특징: 반드시 type 속성(문자열 상수)을 가져야 하며, 추가 데이터는 payload에 넣습니다. 액션 생성자(Action Creator) 함수로 편리하게 생성합니다.

  • 예시: 새로운 할 일을 추가하는 액션.

    // 액션 타입 상수
    const ADD_TODO = 'ADD_TODO';
    
    // 액션 생성자 함수
    const addTodo = (text) => ({
      type: ADD_TODO,
      payload: { text },
    });

    이 액션은 type: 'ADD_TODO'와 할 일 텍스트를 포함합니다.

2. 리듀서 (Reducers)

  • 정의: 현재 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수. 액션 유형에 따라 상태 변경 로직을 정의합니다.
  • 특징: 불변성(Immutability) 을 지켜야 합니다. 기존 상태를 수정하지 말고, 항상 새 객체/배열을 반환하세요. (스프레드 연산자 ...나 Immer 라이브러리 활용)
  • 예시: 할 일 목록 관리 리듀서.
    const todosReducer = (state = [], action) => {
      switch (action.type) {
        case ADD_TODO:
          return [...state, action.payload]; // 새 배열 반환
        default:
          return state; // 변경 없음
      }
    };
    ADD_TODO 시 기존 배열에 새 아이템을 추가해 새 상태를 만듭니다.

3. 저장소 (Store)

  • 정의: 앱의 전체 상태 트리를 담는 단일 객체. 상태 관리, 액션 디스패치, 변경 구독을 담당합니다.

  • 생성: createStore 함수로 만듭니다. (최신 Redux Toolkit에서는 configureStore 추천)

  • 예시:

    import { createStore } from 'redux';
    
    const store = createStore(todosReducer);

4. 액션 디스패치 (Dispatching Actions)

  • 정의: store.dispatch()로 액션을 리듀서에 보냅니다. 상태 변경의 유일한 방법입니다.
  • 예시:
    store.dispatch(addTodo('Redux 배우기'));

5. 상태 가져오기 (Getting State)

  • 정의: store.getState()로 현재 상태를 읽습니다.
  • 예시:
    console.log(store.getState()); // [{ text: 'Redux 배우기' }]

6. 저장소 변경 구독 (Subscribing to Store Changes)

  • 정의: store.subscribe()로 상태 변경 시 콜백을 등록합니다. UI 업데이트에 필수적입니다.

  • 예시:

    const unsubscribe = store.subscribe(() => {
      console.log('상태 업데이트됨:', store.getState());
    });
    
    // 필요 시 구독 해지
    // unsubscribe();

Redux를 활용한 간단한 할 일 앱 예시

이론만으로는 부족하니, Redux로 간단한 할 일 앱(To-Do App)을 만들어 보겠습니다. 할 일 추가/제거 기능을 구현하고, React와 연동해 보죠. (react-redux 라이브러리 사용 가정)

1. 초기 상태와 액션 정의

const initialTodos = [];

// 액션 타입 상수
const ADD_TODO = 'ADD_TODO';
const REMOVE_TODO = 'REMOVE_TODO';

// 액션 생성자 함수
const addTodoAction = (text) => ({ type: ADD_TODO, payload: text });
const removeTodoAction = (index) => ({ type: REMOVE_TODO, payload: index });

2. 액션을 처리하는 리듀서 생성

const todosReducer = (state = initialTodos, action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, action.payload]; // 새로운 할 일 추가 (불변성 유지)
    case REMOVE_TODO:
      // 특정 인덱스의 할 일 제거 (slice로 불변성 유지)
      return [...state.slice(0, action.payload), ...state.slice(action.payload + 1)];
    default:
      return state;
  }
};

3. 저장소 생성

import { createStore } from 'redux';

const store = createStore(todosReducer);

4. React 컴포넌트에서 Redux 사용 (react-redux 활용)

react-redux를 설치(npm install react-redux)하고, Provider로 앱을 감싸세요. 훅(useSelector, useDispatch)으로 상태와 액션을 연결합니다.

  • App.js (메인 컴포넌트):

    import React from 'react';
    import { Provider } from 'react-redux';
    import { store } from './store'; // 저장소 import
    import TodoList from './TodoList';
    
    function App() {
      return (
        <Provider store={store}>
          <TodoList />
        </Provider>
      );
    }
    
    export default App;
  • TodoList.js (할 일 목록 컴포넌트):

    import React, { useState } from 'react';
    import { useSelector, useDispatch } from 'react-redux';
    import { addTodoAction, removeTodoAction } from './actions';
    
    function TodoList() {
      const todos = useSelector((state) => state); // 상태 가져오기
      const dispatch = useDispatch(); // 디스패치 함수
      const [inputText, setInputText] = useState('');
    
      const addTodo = () => {
        if (inputText.trim()) {
          dispatch(addTodoAction(inputText));
          setInputText('');
        }
      };
    
      const removeTodo = (index) => {
        dispatch(removeTodoAction(index));
      };
    
      return (
        <div>
          <h2>할 일 목록</h2>
          <input
            value={inputText}
            onChange={(e) => setInputText(e.target.value)}
            placeholder="새 할 일 입력"
          />
          <button onClick={addTodo}>추가</button>
          <ul>
            {todos.map((todo, index) => (
              <li key={index}>
                {todo}
                <button onClick={() => removeTodo(index)}>삭제</button>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    export default TodoList;

이 예시로 할 일을 추가/제거하면 저장소 상태가 업데이트되고, UI가 자동으로 재렌더링됩니다. Redux DevTools를 설치하면 액션 히스토리를 실시간으로 볼 수 있어요!

Redux의 장점과 고려사항

Redux는 대규모 앱에서 빛을 발하지만, 소규모 앱에는 오버엔지니어링일 수 있습니다. 최근에는 ZustandRecoil 같은 가벼운 대안이 인기예요. Redux를 사용할 때는 Redux Toolkit을 추천합니다 – 보일러플레이트 코드를 줄여주고, Immer로 불변성을 자동 처리하죠.

728x90