게으른 개발자의 끄적거림

리덕스란? (Redux 완벽 정리)

끄적잉 2025. 7. 30. 09:54

리덕스란!???

 

 웹 개발의 세계는 끊임없이 진화하고 있으며, 특히 사용자 인터랙션이 많아지고 데이터 흐름이 복잡해지는 현대 웹 애플리케이션에서는 '상태 관리(State Management)' 가 개발의 성패를 좌우하는 핵심 요소가 되었습니다. 오늘 이 블로그 포스팅에서는 React와 같은 프런트엔드 라이브러리/프레임워크와 환상의 궁합을 자랑하며, 전역 상태 관리에 있어 독보적인 위치를 차지하고 있는 라이브러리, Redux에 대해 깊이 있게 알아보겠습니다.

단순히 사용법을 나열하는 것을 넘어, Redux가 왜 필요하고 어떤 철학을 가지고 있으며, 여러분의 애플리케이션에 어떻게 강력한 힘을 실어줄 수 있는지 자세히 파헤쳐 보겠습니다. 이 글을 통해 Redux의 본질을 이해하고, 여러분의 다음 프로젝트에 자신 있게 적용할 수 있기를 바랍니다.


1. Redux, 왜 필요한가요? - 상태 관리의 복잡성

모던 웹 애플리케이션은 사용자 로그인 상태, 장바구니 품목, UI 테마, 데이터 로딩 상태 등 다양한 종류의 '상태'를 가집니다. 애플리케이션의 규모가 커지고 컴포넌트 간의 관계가 복잡해질수록 다음과 같은 문제가 발생합니다.

  • 컴포넌트 간의 상태 공유 어려움 (Prop Drilling): 상위 컴포넌트에서 하위 컴포넌트로 상태를 전달하기 위해 여러 단계를 거쳐 Props를 '드릴'처럼 뚫고 내려가야 합니다. 이는 코드를 읽기 어렵게 만들고 유지보수를 힘들게 합니다.
  • 예측 불가능한 상태 변화: 여러 컴포넌트에서 동일한 상태를 변경할 수 있게 되면, 어떤 컴포넌트가 언제 어떻게 상태를 변경했는지 추적하기 어려워집니다. 이는 버그 발생의 주원인이 됩니다.
  • 디버깅의 어려움: 상태가 여기저기 흩어져 있거나 비동기적으로 복잡하게 변경되면, 버그 발생 시 문제의 원인을 찾아내기가 매우 어려워집니다.

Redux는 이러한 문제들을 해결하기 위해 탄생했습니다. 애플리케이션의 모든 상태를 예측 가능하고 일관된 방식으로 관리하는 단일 저장소(Store)를 제공하는 것이 핵심입니다.


2. Redux의 세 가지 핵심 원칙 (Principles)

Redux는 세 가지 매우 중요한 원칙을 기반으로 합니다. 이 원칙들을 이해하는 것이 Redux를 제대로 활용하는 첫걸음입니다.

  1. 단일 진실 공급원 (Single Source of Truth):
    • 애플리케이션의 모든 상태는 하나의 커다란 자바스크립트 객체 트리 형태로 하나의 Store 안에 저장됩니다.
    • 마치 은행에 하나의 중앙 데이터베이스가 모든 고객의 잔액 정보를 관리하듯이, Redux는 애플리케이션의 모든 데이터를 한 곳에서 관리합니다. 이는 상태의 일관성과 예측 가능성을 극대화합니다.
  2. 상태는 읽기 전용이다 (State is Read-Only):
    • 상태를 직접 변경할 수 없습니다. 상태를 변경하고 싶다면, 반드시 액션(Action) 이라는 일반 자바스크립트 객체를 통해 변경 의도를 표현해야 합니다.
    • 마치 법률 시스템에서 모든 변경 요청이 공식적인 서류(액션)를 통해 이루어지는 것처럼, Redux의 상태 변경은 투명하고 기록 가능합니다. 이는 어떤 변경이 왜 일어났는지 추적하기 쉽게 만듭니다.
  3. 변화는 리듀서에 의해 이루어져야 한다 (Changes are Made with Pure Functions / Reducers):
    • 액션에 의해 상태가 어떻게 변경될지는 리듀서(Reducer) 라는 순수 함수(Pure Function) 에 의해 정의됩니다.
    • 순수 함수란:
      • 동일한 입력(State와 Action)에는 항상 동일한 출력(새로운 State)을 반환합니다.
      • 함수 외부의 어떤 것도 변경하지 않습니다 (Side Effect 없음).
    • 리듀서는 현재 상태와 액션을 받아서, 새로운 상태를 반환합니다. 이 과정에서 기존 상태를 직접 변경(Mutation)하지 않고, 항상 새로운 상태 객체를 생성하여 반환해야 합니다.
    • 이는 예측 가능한 상태 변화를 보장하고, 디버깅을 용이하게 하며, 시간 여행 디버깅(Time-Travel Debugging)과 같은 강력한 도구를 가능하게 합니다.

3. Redux의 주요 구성 요소 (Core Components)

Redux는 몇 가지 핵심적인 구성 요소들이 유기적으로 연결되어 동작합니다.

  1. Store (스토어):
    • Redux의 핵심이자 모든 상태를 담고 있는 단 하나의 객체입니다.
    • 상태를 저장하고, 상태를 업데이트하며, 상태 변경을 구독(Subscribe)할 수 있는 메서드를 제공합니다.
  2. Action (액션):
    • 상태 변경을 위한 "무엇이 일어났는지"를 설명하는 객체입니다.
    • 필수적으로 type 속성을 가져야 하며, 이 type은 어떤 종류의 액션인지 문자열로 정의합니다. (예: { type: 'ADD_TODO', text: 'Learn Redux' })
    • 액션은 상태를 직접 변경하지 않고, 단지 변경 "의도"만을 전달합니다.
  3. Reducer (리듀서):
    • 액션을 받아서 "상태를 어떻게 변경할지"를 정의하는 순수 함수입니다.
    • (previousState, action) => newState 형태를 가집니다.
    • 이전 상태와 액션을 인자로 받아 새로운 상태를 반환합니다. 절대 이전 상태를 직접 변경해서는 안 됩니다.
  4. Dispatcher (디스패처 - Store의 메서드):
    • store.dispatch(action) 메서드를 통해 액션을 Redux Store로 보내는(전달하는) 역할을 합니다.
    • 이 메서드가 호출되면, Redux는 해당 액션을 모든 리듀서에게 전달하여 상태를 업데이트합니다.
  5. Selector (셀렉터):
    • Store의 상태(State)에서 원하는 데이터를 추출하는 함수입니다.
    • UI 컴포넌트가 Store에서 필요한 데이터만 효율적으로 가져올 수 있도록 돕습니다.

4. Redux의 동작 흐름 (Flow)

Redux의 데이터 흐름은 단방향(Unidirectional Data Flow)입니다. 이 일관된 흐름은 상태 변화를 예측 가능하게 만듭니다.

  1. 사용자 인터랙션 발생: UI(React 컴포넌트)에서 사용자의 클릭, 입력 등 어떤 이벤트가 발생합니다.
  2. Action Dispatch: 이벤트 핸들러는 store.dispatch(action) 메서드를 호출하여 상태 변경을 위한 Action 객체를 Store로 보냅니다.
  3. Reducer 호출: Store는 전달받은 Action을 모든 Reducer 함수에게 전달합니다. (실제로는 Root Reducer에게 전달되고, Root Reducer가 하위 Reducer들에게 전달합니다.)
  4. State 변경: Reducer는 현재 상태와 Action을 기반으로 새로운 상태 객체를 생성하여 반환합니다. (기존 상태는 변경하지 않음)
  5. Store 업데이트: Store는 Reducer가 반환한 새로운 상태로 자신의 상태를 업데이트합니다.
  6. UI 렌더링: Store의 상태가 변경되면, Store를 구독(Subscribe)하고 있던 UI 컴포넌트들이 변경된 상태를 감지하고, 필요한 경우 새로운 데이터로 다시 렌더링됩니다.

이 흐름은 항상 동일하게 반복되며, 어떤 상태 변화든 이 흐름을 따라가기 때문에 디버깅이 매우 용이합니다.


 

5. Redux의 장점 (Advantages)

  • 예측 가능한 상태 관리: 단일 진실 공급원, 읽기 전용 상태, 순수 리듀서라는 원칙 덕분에 상태 변화가 매우 예측 가능하며 일관적입니다.
  • 쉬운 디버깅: 모든 상태 변화가 액션이라는 기록으로 남아 있기 때문에, Redux DevTools를 사용하면 상태 변화의 과정을 시간 여행하듯이 추적하고 재현할 수 있습니다. 이는 버그를 찾는 데 엄청난 도움이 됩니다.
  • 중앙 집중식 상태: 모든 상태가 한 곳에 모여 있어 컴포넌트 간의 상태 공유가 매우 효율적입니다. Props Drilling 문제를 해결할 수 있습니다.
  • 유지보수 용이성: 상태 변화 로직이 리듀서에 명확히 분리되어 있어, 코드의 가독성이 높아지고 유지보수가 용이해집니다.
  • 서버 사이드 렌더링(SSR) 지원: 서버와 클라이언트 간의 상태 동기화를 쉽게 할 수 있어 SSR에 유리합니다.
  • 강력한 미들웨어 생태계: 비동기 작업(API 호출 등)을 처리하기 위한 Redux Thunk, Redux Saga와 같은 미들웨어와, 로깅, 크래시 리포팅 등을 위한 다양한 미들웨어를 활용할 수 있습니다.

6. Redux의 단점 및 고려 사항 (Disadvantages & Considerations)

Redux는 만능이 아니며, 모든 프로젝트에 필수적인 것은 아닙니다.

  • 보일러플레이트 코드 (Boilerplate Code): 특히 초기 설정 시 액션 타입, 액션 생성자, 리듀서 등 작성해야 할 코드가 많아 복잡하게 느껴질 수 있습니다. 이는 Redux Toolkit의 등장으로 많이 해소되었습니다.
  • 학습 곡선: 초보 개발자에게는 Redux의 개념(액션, 리듀서, 미들웨어 등)과 단방향 데이터 흐름을 이해하는 데 시간이 필요할 수 있습니다.
  • 과도한 사용: 작은 규모의 애플리케이션에서는 Redux를 도입하는 것이 오히려 복잡성을 증가시킬 수 있습니다. 컴포넌트 내부 상태(Local State)나 React Context API만으로도 충분한 경우가 많습니다.
  • 성능 최적화: 상태가 변경될 때마다 관련 컴포넌트가 다시 렌더링될 수 있으므로, React.memo, useCallback, useMemo 등을 활용한 렌더링 최적화에 신경 써야 합니다. (물론 React 자체의 문제입니다.)

7. Redux Toolkit: Redux 개발을 더 쉽고 효율적으로!

Redux의 보일러플레이트와 복잡성 문제를 해결하기 위해 공식적으로 권장되는 도구 모음이 바로 Redux Toolkit (RTK) 입니다. RTK는 다음과 같은 주요 기능들을 제공하여 Redux 개발 경험을 획기적으로 개선합니다.

  • configureStore: Redux Store를 쉽게 설정할 수 있으며, Redux Thunk와 Redux DevTools Extension이 기본으로 포함됩니다.
  • createSlice: 액션 타입, 액션 생성자, 리듀서를 한 번에 정의할 수 있게 해주는 함수입니다. 이를 통해 보일러플레이트 코드를 대폭 줄일 수 있습니다. (상태를 '슬라이스' 단위로 관리)
  • createAsyncThunk: 비동기 로직(API 호출 등)을 쉽게 처리할 수 있는 액션 생성자를 생성합니다.
  • createEntityAdapter: 정규화된 상태를 관리할 때 유용합니다.

Redux를 새로 시작하거나 기존 프로젝트에 도입한다면, 반드시 Redux Toolkit을 사용하는 것을 강력히 권장합니다.


마무리하며

Redux는 복잡한 웹 애플리케이션의 상태 관리를 위한 매우 강력하고 성숙한 라이브러리입니다. 그 예측 가능한 단방향 데이터 흐름과 단일 진실 공급원이라는 철학은 대규모 프로젝트에서 빛을 발하며, 효율적인 디버깅과 유지보수를 가능하게 합니다.

물론, Redux가 모든 프로젝트의 만병통치약은 아니며, 작은 규모의 애플리케이션에서는 과도한 오버헤드일 수 있습니다. 하지만 여러분의 애플리케이션이 성장하고 상태 관리가 점점 더 복잡해진다면, Redux는 여러분에게 질서와 통제력을 제공하는 든든한 아군이 될 것입니다.