프론트엔드에서 애플리케이션이 복잡해질수록 “상태 관리”는 개발 난이도를 결정하는 핵심 요소가 된다.
초기 React에서는 각 컴포넌트 내부에서 state를 관리해도 큰 문제가 없었지만, 애플리케이션 규모가 커지는 순간 상황은 완전히 달라진다.
이 글에서는 Redux가 등장한 이유, 핵심 동작 원리, Redux Toolkit, 그리고 그 토대가 되는 개념인 전역 상태 관리(Global State Management)까지 한 번에 정리한다.
애플리케이션 내의 상태(state)는 크게 두 가지로 나뉜다.
특정 컴포넌트 내부에서만 사용하는 값
예: 모달 열림/닫힘, input 입력값, 개별 UI 선택 상태 등
여러 컴포넌트에서 동시에 사용하는 값
예:
React 기본 구조에서 이를 관리하려고 하면 props drilling 문제가 발생한다.
또한 여러 위치에서 동일한 데이터를 각각 관리하면 데이터 일관성(Inconsistency)이 쉽게 깨진다.
따라서 애플리케이션 전반에서 공통으로 쓰이는 데이터는 한 곳에서 관리해야 한다.
이를 전역 상태 관리(Global State Management)라고 한다.
그리고 Redux는 이러한 전역 상태 문제를 체계적으로 해결하는 도구다.
Redux는 전역 상태를 예측 가능하게 관리하기 위한 상태 관리 라이브러리이다.
주로 React와 함께 사용되지만, 특정 프레임워크에 종속되지 않으며
어떤 JavaScript 환경에서도 사용할 수 있다.
Redux의 핵심 철학은 다음 세 가지이다.
React만으로도 상태 관리는 가능하지만, 애플리케이션의 규모가 커질수록 문제점이 드러난다.
상태를 전달하기 위해 부모 → 자식 → 손자 컴포넌트로 이어지는 불필요한 props가 계속 등장한다.
이 과정은 컴포넌트 구조를 복잡하게 만들고 유지보수를 어렵게 한다.
여러 컴포넌트에서 동일한 데이터를 각각 관리하게 되면, 상태가 서로 일치하지 않는 문제가 발생한다.
이런 상태 불일치는 디버깅이 어려운 버그로 이어진다.
상태가 여러 곳에서 변경되면 어떻게 바뀐 것인지 추적하기가 어렵다.
애플리케이션이 커질수록 이러한 문제는 더욱 복잡해진다.
Redux는 이 모든 문제를 해결하기 위해 설계된 라이브러리이다.
전역 상태의 흐름을 단일한 규칙 아래 통제하여 상태 변화를 예측 가능하게 만든다.
Redux는 네 가지 주요 요소로 구성된다.
애플리케이션의 모든 전역 상태가 저장되는 중앙 저장소이다.
store는 단 하나만 존재하며, 전역 상태의 단일 출처 역할을 한다.
상태를 어떻게 변경할 것인지를 설명하는 “의도(intent)” 객체이다.
{ type: "cart/addItem", payload: item }
action을 받아 실제로 상태를 변경하는 순수 함수이다.
상태 변경 로직은 reducer 내부에서 정의된다.
function cartReducer(state, action) {
switch (action.type) {
case "cart/addItem":
return [...state, action.payload];
default:
return state;
}
}
Reducer는 기존 상태를 직접 수정하지 않고,
항상 새로운 상태 객체를 반환한다는 점이 중요하다.
dispatch는 action을 store로 전달하는 함수이다.
UI에서 발생한 이벤트를 store로 보내는 통로라고 보면 된다.
dispatch({ type: "cart/addItem", payload: item });
dispatch가 호출되면 reducer가 실행되어 새로운 상태가 계산되고,
이를 기반으로 UI가 다시 렌더링된다.
Redux는 단방향 데이터 흐름(one-way data flow)을 사용한다.
UI → Action → Dispatch → Reducer → Store → UI 업데이트
이 단일 흐름 덕분에 상태가 언제, 어떻게 바뀌었는지 쉽게 추적할 수 있다.
또한 데이터 흐름이 너무 복잡해지는 것을 방지하여 애플리케이션의 안정성을 높인다.
아래는 간단한 Counter 예시이다.
Action
{ type: "counter/increment" }
Reducer
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case "counter/increment":
return { value: state.value + 1 };
default:
return state;
}
}
Dispatch
dispatch({ type: "counter/increment" });
기존 Redux는 강력했지만, 실제 사용 과정에서 다음과 같은 불편함이 있었다.
action type 문자열을 매번 작성해야 함 / action creator 함수를 직접 만들어야 함 /reducer에서 불변성 관리를 일일이 처리해야 함
이 문제를 해결하기 위해 등장한 것이 Redux Toolkit(RTK)이다.
RTK는 다음 기능을 자동으로 처리한다.
1.action type 및 action creator 자동 생성
2.불변성 관리 자동화 (Immer 기반)
3.thunk 내장
4.slice 구조로 파일 단순화
Redux Toolkit은 현대 Redux 개발의 사실상 표준이며, 새로운 Redux 프로젝트는 대부분 RTK를 사용한다.
Redux는 강력한 도구이지만, 모든 프로젝트에 필요한 것은 아니다.
✔ Redux가 적합한 경우
애플리케이션 규모가 크고 상태가 복잡할 때
여러 컴포넌트에서 동일한 데이터를 공유할 때
상태 변경 이력을 명확하게 추적해야 할 때
DevTools를 통한 디버깅이 중요한 프로젝트일 때
❌ Redux가 필요 없는 경우
상태가 간단한 소규모 프로젝트
전역 상태가 거의 없을 때
서버 상태가 중심인 프로젝트 (TanStack Query가 적합)
Redux는 전역 상태 관리 문제를 가장 체계적으로 해결한 라이브러리이다.
특히 Redux Toolkit의 등장 이후 Redux는 훨씬 간결해지고 사용성이 좋아졌다.
전역 상태가 많고, 여러 컴포넌트에서 공유되며,상태 변경 과정을 예측 가능하게 유지하고 싶다면Redux는 지금도 여전히 매우 좋은 선택지다.