프로그래밍/ReactJS

Redux 리듀서: 복잡한 React 상태 관리를 위한 핵심 도구

shimdh 2025. 10. 18. 09:57
728x90

안녕하세요, 프론트엔드 개발자 여러분! React로 복잡한 애플리케이션을 개발하다 보면, 상태(state) 관리가 가장 골치 아픈 부분이죠. 컴포넌트들이 서로 얽히며 상태를 공유하고 변경할 때마다 버그가 발생하거나 코드가 엉망이 되는 경우가 많아요. 이런 문제를 해결해주는 강력한 무기가 바로 Redux 입니다. Redux의 심장부라고 할 수 있는 리듀서(reducer) 를 중심으로, 오늘은 상태 관리의 비밀을 풀어보려 해요. 이 글을 통해 리듀서의 원리를 이해하고, 실제로 적용해보며 React 개발의 생산성을 높여보세요!

리듀서란 무엇인가요?

Redux의 리듀서는 애플리케이션의 전역 상태를 중앙 집중식으로 관리하는 핵심 함수예요. 간단히 말해, 현재 상태(state)와 액션(action)을 입력으로 받아 새로운 상태를 반환하는 순수 함수입니다. 컴포넌트에서 발생하는 사용자 이벤트나 API 응답 같은 액션을 "디스패치"하면, 리듀서가 이를 처리해 상태를 업데이트하죠. 이 과정이 예측 가능하고 일관되게 동작하기 때문에, 대규모 앱에서도 안정적인 상태 관리가 가능합니다.

리듀서는 Redux의 "단방향 데이터 흐름" 철학을 구현하는 역할을 해요. 액션 → 리듀서 → 상태 업데이트 → 컴포넌트 재렌더링이라는 흐름으로, 상태 변경의 출처를 쉽게 추적할 수 있어요.

728x90

리듀서의 주요 특징

리듀서가 왜 Redux의 성공 비결인지 이해하려면, 그 핵심 특징을 알아야 해요. 아래 세 가지가 리듀서의 강력함을 뒷받침합니다.

  1. 순수 함수(Pure Function)
    리듀서는 부작용(side effect)이 전혀 없어야 해요. API 호출, DOM 조작, 외부 변수 수정 같은 작업은 금지! 오직 입력된 상태와 액션만으로 새로운 상태를 계산합니다. 이 덕분에 동일한 입력에 항상 동일한 출력을 보장하여, 앱의 동작이 예측 가능해집니다. 예를 들어, 테스트 시 상태를 쉽게 모킹할 수 있어요.
  2. 불변성(Immutability)
    기존 상태를 직접 수정하지 말고, 새로운 객체를 생성해 반환하세요. JavaScript의 스프레드 연산자(...state)나 Immer 같은 라이브러리를 활용하면 편리해요. 이 원칙은 시간 여행 디버깅(time-travel debugging)을 가능하게 하며, 상태 변경 이력을 Redux DevTools로 시각화할 수 있습니다. 변경 추적이 쉬워지니, 버그 사냥이 훨씬 수월해집니다!
  3. 단일 책임(Single Responsibility)
    각 리듀서는 앱 상태의 한 부분만 관리합니다. 예를 들어, 사용자 인증은 authReducer, 쇼핑카트는 cartReducer로 분리하세요. 이를 combineReducers로 결합하면 루트 리듀서가 완성되죠. 이 접근은 코드의 모듈성을 높여 유지보수와 확장성을 강화합니다.

리듀서의 구조

리듀서의 기본 구조는 switch 문으로 액션 타입을 처리하는 패턴을 따릅니다. 아래는 간단한 카운터 앱의 리듀서 예시예요. 초기 상태(initialState)를 정의하고, 액션에 따라 상태를 업데이트하죠. 매치되지 않는 액션은 기존 상태를 그대로 반환해 안전성을 확보합니다.

const initialState = {
  count: 0,
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

이 코드에서 보듯, 불변성을 지키기 위해 ...state로 복사 후 count만 업데이트합니다. 실제 프로젝트에서는 액션 크리에이터(Action Creator)를 사용해 타입을 상수로 관리하면 더 깔끔해져요.

실용적인 예시: Redux 리듀서로 카운터 애플리케이션 구축하기

이론만으로는 부족하죠? 이제 Redux 리듀서를 활용해 React에서 완전한 카운터 앱을 만들어 보겠습니다. 단계별로 따라 해보세요. (필요한 패키지: redux, react-redux 설치 가정)

1. 액션 설정

액션은 상태 변경을 트리거하는 일반 JS 객체예요. 타입(type)이 필수입니다.

// 액션 크리에이터
export const incrementAction = () => ({ type: 'INCREMENT' });
export const decrementAction = () => ({ type: 'DECREMENT' });

2. 스토어 생성

createStore로 중앙 스토어를 만듭니다. (Redux Toolkit을 사용하면 더 간단하지만, 여기서는 기본으로 진행)

import { createStore } from 'redux';
import counterReducer from './counterReducer'; // 위 리듀서 파일

const store = createStore(counterReducer);
export default store;

3. React 컴포넌트를 Redux 스토어에 연결

react-redux의 훅(useSelector, useDispatch)으로 상태 읽기/쓰기를 연결합니다. <Provider>로 앱을 감싸세요.

import React from 'react';
import { Provider, useDispatch, useSelector } from 'react-redux';
import store from './store';
import { incrementAction, decrementAction } from './actions';

const CounterComponent = () => {
  const dispatch = useDispatch();
  const count = useSelector((state) => state.count);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(incrementAction())}>증가</button>
      <button onClick={() => dispatch(decrementAction())}>감소</button>
    </div>
  );
};

const App = () => (
  <Provider store={store}>
    <CounterComponent />
  </Provider>
);

export default App;

이제 앱을 실행하면 버튼 클릭 시 리듀서가 액션을 처리해 상태가 업데이트되고, 컴포넌트가 재렌더링됩니다. Redux DevTools를 설치하면 액션 흐름을 실시간으로 볼 수 있어요!

Redux 리듀서를 사용하는 이점

리듀서를 도입하면 React 개발이 한 단계 업그레이드됩니다. 주요 이점은 다음과 같아요:

  • 예측 가능한 상태 관리: 모든 변경이 액션과 리듀서를 통해 이뤄지니, 데이터 흐름이 명확해집니다. 복잡한 앱에서도 "왜 이 상태가 됐지?"라는 의문이 사라져요.
  • 쉬운 테스트: 순수 함수라서 단위 테스트가 간단합니다. Jest로 expect(reducer(initialState, action)).toEqual(expectedState)처럼 검증하세요.
  • 관심사의 분리: combineReducers로 여러 리듀서를 통합하면, 대형 앱의 상태를 논리적으로 쪼개 관리할 수 있습니다. 팀 개발 시 충돌도 줄어요.

추가로, Redux Toolkit(RTK)을 사용하면 보일러플레이트 코드를 줄이고, 리듀서 작성을 더 직관적으로 만들 수 있어요. 2025년 현재, RTK Query 같은 기능으로 API 상태 관리까지 커버합니다.

728x90