문서의 각 단락이 어느 리비전에서 마지막으로 수정되었는지 확인할 수 있습니다. 왼쪽의 정보 칩을 통해 작성자와 수정 시점을 파악하세요.

상태 관리 패러다임 및 Flux 패턴 | |
이름 | |
분류 | |
주요 개념 | 단방향 데이터 흐름, Action, Dispatcher, Store, View |
제안자 | |
주요 사용 사례 | React 애플리케이션의 상태 관리 |
핵심 원칙 | 데이터의 단방향 흐름을 통해 예측 가능한 상태 변화 |
상세 정보 | |
등장 배경 | MVC 패턴의 복잡한 데이터 흐름과 양방향 바인딩으로 인한 예측 불가능한 상태 변화 문제 해결을 위해 제안됨 |
데이터 흐름 | Action → Dispatcher → Store → View |
Action | 상태를 변경하려는 의도를 나타내는 객체 |
Dispatcher | |
Store | 애플리케이션의 상태와 상태 변경 로직을 보유 |
View | 상태를 기반으로 UI를 렌더링하며, Store의 변경을 구독 |
장점 | 데이터 흐름이 명확하고, 상태 변화를 추적 및 디버깅하기 쉬움 |
단점 | 보일러플레이트 코드가 많을 수 있음 |
관련 라이브러리/구현체 | |
대안 패러다임 | |

상태 관리 패러다임은 현대 웹 애플리케이션과 프론트엔드 개발의 핵심 과제 중 하나이다. 애플리케이션의 규모와 복잡성이 증가함에 따라, 다양한 컴포넌트들이 공유하는 데이터를 어떻게 효율적이고 예측 가능하게 관리할지에 대한 고민이 깊어졌다. 이 과정에서 등장한 여러 패턴 중, 페이스북(현 메타)이 제시한 Flux 패턴은 단방향 데이터 흐름을 강조하며 상태 관리의 새로운 기준을 제시했다.
Flux는 기존의 MVC 패턴이 대규모 애플리케이션에서 발생시키는 데이터 흐름의 복잡성과 예측 불가능성을 해결하기 위해 고안되었다. 이 패턴은 데이터가 한 방향으로만 순환하도록 강제함으로써 상태 변화를 명시적이고 추적 가능하게 만든다. Flux의 등장은 Redux, MobX, Vuex 등 수많은 상태 관리 라이브러리와 프레임워크에 직접적인 영감을 주었으며, 오늘날 프론트엔드 생태계의 근간을 이루는 개념이 되었다.
이 문서는 상태 관리의 기본 개념에서 출발하여 Flux 패턴의 핵심 원리, 주요 구현체들의 비교, 그리고 최신 동향까지를 체계적으로 살펴본다. 최종 목표는 개발자가 프로젝트의 요구사항에 가장 적합한 상태 관리 도구와 전략을 선택하는 데 실질적인 가이드를 제공하는 것이다.

상태는 애플리케이션이 특정 시점에 가지고 있는 데이터나 정보의 집합을 의미한다. 이는 사용자 입력, 서버 응답, UI의 표시 여부 등 애플리케이션의 모든 변화 가능한 부분을 포함한다. 상태가 변경되면 이에 반응하여 UI가 갱신되어 사용자에게 새로운 화면을 보여주게 된다.
전통적인 MVC 패턴에서는 모델(Model), 뷰(View), 컨트롤러(Controller)가 서로 양방향으로 연결되어 데이터를 주고받는다. 이 구조는 애플리케이션 규모가 커질수록 데이터 흐름을 추적하기 어려워지는 문제를 야기한다. 하나의 상태 변경이 여러 뷰(View)를 통해 다른 모델(Model)을 업데이트하고, 이는 다시 다른 뷰(View)에 영향을 미치는 복잡한 상호작용이 발생할 수 있다. 이로 인해 버그가 발생했을 때 그 원인을 찾기 힘들고, 애플리케이션의 동작을 예측하기 어려워진다[1].
이러한 한계를 해결하기 위해 등장한 핵심 개념이 바로 단방향 데이터 흐름(Unidirectional Data Flow)이다. 상태는 특정한 경로를 따라 한 방향으로만 흐르도록 제한하여, 데이터의 변화와 UI 업데이트 사이의 인과 관계를 명확하게 만든다. 이는 애플리케이션의 동작을 더 예측 가능하고 디버깅을 용이하게 만드는 기반이 된다.
상태는 애플리케이션이 특정 시점에 가지고 있는 모든 데이터의 스냅샷이다. 이는 사용자가 입력한 폼 데이터, 서버에서 받아온 API 응답, UI의 표시/숨김 여부와 같은 인터페이스 정보, 페이지 라우팅 정보 등을 포함한다. 상태가 변경되면 애플리케이션의 동작이나 화면에 보이는 내용이 그에 따라 반응하여 변한다.
상태는 일반적으로 지역 상태(Local State)와 전역 상태(Global State)로 구분된다. 지역 상태는 특정 컴포넌트 내부에서만 관리되고 사용되는 데이터를 말한다. 예를 들어, 입력 필드의 값이나 모달 창의 열림/닫힘 상태가 이에 해당한다. 반면 전역 상태는 애플리케이션의 여러 부분(다수의 컴포넌트)에서 공유되어야 하는 데이터를 의미한다. 사용자 인증 정보, 애플리케이션 테마, 전역적인 알림 메시지, 장바구니 내용 등이 전역 상태의 대표적인 예이다.
상태 관리의 핵심은 상태의 변화를 어떻게 예측 가능하고 추적 가능하게 만드는가에 있다. 상태가 변경되는 경로와 이유가 명확하지 않으면, 애플리케이션은 버그가 발생하기 쉽고 디버깅이 어려워진다. 특히 단일 페이지 애플리케이션(SPA)이 복잡해지면서 상태의 규모와 변화의 빈도가 증가함에 따라 체계적인 상태 관리의 필요성이 대두되었다.
상태 유형 | 설명 | 예시 |
|---|---|---|
지역 상태 (Local State) | 단일 컴포넌트 범위에서 관리되는 상태. | 입력 필드 값, 토글 버튼 상태 |
전역 상태 (Global State) | 여러 컴포넌트에서 공유되어야 하는 상태. | 사용자 로그인 정보, 애플리케이션 테마 |
서버 상태 (Server State) | 백엔드 서버에서 비동기적으로 가져오는 상태. | 사용자 프로필 데이터, 게시물 목록 |
URL 상태 (URL State) | 브라우저 주소창에 존재하는 상태. | 현재 페이지 경로, 쿼리 파라미터 |
전통적인 MVC 패턴은 소프트웨어의 구조를 모델(Model), 뷰(View), 컨트롤러(Controller)로 분리하여 관심사를 분리하는 데 성공했다. 특히 서버 사이드 애플리케이션에서 널리 사용되었으며, 각 구성 요소가 명확한 역할을 가진다는 장점이 있었다.
그러나 복잡한 사용자 인터페이스를 가진 클라이언트 사이드 애플리케이션, 특히 단일 페이지 애플리케이션(SPA)에서는 한계가 드러났다. 가장 큰 문제는 양방향 데이터 흐름으로 인한 복잡성과 예측 불가능성이다. 뷰와 모델 사이에 다수의 상호작용 경로가 생기면, 하나의 상태 변경이 여러 컴포넌트를 통해 연쇄적으로 전파되어 데이터 흐름을 추적하기 어려워진다. 이는 "스파게티 코드" 현상을 초래하고, 애플리케이션의 디버깅과 유지보수를 매우 힘들게 만든다.
또 다른 문제점은 데이터의 변화를 추적하는 로직이 애플리케이션 전반에 흩어져 있다는 것이다. 모델의 상태가 변경될 수 있는 지점이 여러 곳이기 때문에, 특정 상태 변화가 어디서, 왜 발생했는지 명확히 파악하기 어렵다. 이로 인해 애플리케이션의 동작을 예측하기 힘들고, 새로운 기능을 추가할 때 의도치 않은 부작용이 발생할 위험이 크다.
이러한 한계를 요약하면 다음과 같다.
한계점 | 설명 |
|---|---|
양방향 데이터 흐름 | 모델과 뷰가 서로 직접 영향을 주고받아 데이터 흐름이 복잡해지고 추적이 어려움. |
예측 불가능한 상태 변화 | 상태 변경의 출처와 경로가 명확하지 않아 애플리케이션 동작을 이해하고 디버깅하기 힘듦. |
유지보수의 어려움 | 로직이 분산되어 있어 코드의 의도를 파악하거나 새로운 기능을 추가하기 복잡함. |
이러한 문제점을 해결하기 위해 등장한 새로운 패러다임이 바로 단방향 데이터 흐름을 강제하는 Flux 패턴이다.

Flux 패턴은 페이스북이 복잡한 클라이언트 사이드 애플리케이션의 데이터 흐름 문제를 해결하기 위해 제안한 아키텍처 패턴이다. 이 패턴의 가장 큰 특징은 데이터의 흐름이 단 하나의 방향으로만 진행된다는 점이다. 이는 기존의 MVC 패턴에서 발생하던 양방향 데이터 바인딩으로 인한 복잡성과 예측 불가능한 상태 변화를 근본적으로 차단한다.
Flux 패턴은 크게 네 가지 구성 요소로 이루어진다. 사용자 인터랙션은 액션(Action)을 생성하며, 이는 순수한 객체 형태로 어떤 변화가 필요한지를 설명한다. 생성된 액션은 디스패처(Dispatcher)라는 중앙 허브로 전달된다. 디스패처는 모든 액션을 받아 등록된 스토어(Store)에 전파하는 역할을 한다. 스토어는 애플리케이션의 상태와 비즈니스 로직을 담당하며, 디스패처로부터 액션을 받아 그에 따라 자신의 상태를 변경한다. 마지막으로 상태가 변경된 스토어는 자신을 구독하고 있는 뷰(View)에 변경 사실을 알리고, 뷰는 새로운 상태를 반영하여 화면을 갱신한다.
이 흐름을 단계별로 정리하면 다음과 같다.
단계 | 구성 요소 | 역할 |
|---|---|---|
1. 사용자 인터랙션 | 뷰 | 버튼 클릭 등 이벤트 발생 |
2. 액션 생성 | 액션 생성자 |
|
3. 액션 전달 | 디스패처 | 모든 액션을 스토어에 전파하는 중앙 허브 |
4. 상태 변경 | 스토어 | 액션 타입에 따라 상태를 변경하는 비즈니스 로직 실행 |
5. 화면 갱신 | 뷰 | 스토어의 변경 알림을 받고 새 상태로 리렌더링 |
이러한 단방향 순환 구조는 애플리케이션의 상태 변화를 명확하게 추적할 수 있게 한다. 모든 상태 변화는 반드시 액션을 통해 시작되며, 그 경로는 액션 -> 디스패처 -> 스토어 -> 뷰로 고정된다. 이로 인해 버그가 발생했을 때 어디서 상태가 잘못 변경되었는지 디버깅하기가 훨씬 수월해진다. 또한, 스토어는 외부에서 상태를 직접 변경할 수 없고 오직 디스패처를 통한 액션에만 반응하므로, 상태의 무결성을 보장하는 데 유리하다.
단방향 데이터 흐름은 Flux 패턴의 가장 핵심적인 설계 원칙이다. 이는 데이터의 흐름이 한 방향으로만 진행되도록 제한함으로써 애플리케이션의 상태 변화를 보다 예측 가능하고 이해하기 쉽게 만든다. 전통적인 MVC 패턴에서는 뷰와 모델이 양방향으로 직접 연결되어 있어, 하나의 상태 변경이 여러 경로를 통해 다른 부분에 영향을 미칠 수 있어 데이터 흐름을 추적하기 어려웠다. 단방향 흐름은 이러한 복잡성과 불확실성을 해결하기 위해 도입되었다.
Flux의 단방향 데이터 흐름은 액션 → 디스패처 → 스토어 → 뷰라는 명확한 사이클을 따른다. 사용자 인터랙션 등으로 인해 액션(Action)이 생성되면, 이는 디스패처(Dispatcher)를 통해 모든 스토어(Store)에 전달된다. 스토어는 액션의 타입을 확인하고 그에 따라 자신이 관리하는 상태를 업데이트한다. 최종적으로 상태가 변경된 스토어는 자신을 구독하고 있는 뷰(View)에 변경 사실을 알리고, 뷰는 새로운 상태를 반영하여 화면을 다시 그린다. 이 과정에서 데이터는 절대 역방향으로 흐르지 않는다.
이 구조의 주요 이점은 상태 변화의 추적과 디버깅이 용이해진다는 점이다. 모든 상태 변경은 반드시 액션을 통해서만 발생하며, 액션은 기록(log)될 수 있다. 따라서 애플리케이션에서 특정 버그가 발생했을 때, 어떤 액션의 연쇄적 실행이 그 상태를 만들어냈는지 시간 순서대로 따라갈 수 있다. 이는 복잡한 애플리케이션에서 데이터의 일관성을 유지하고 사이드 이펙트를 최소화하는 데 결정적인 역할을 한다.
단방향 흐름은 아래와 같이 도식화하여 이해할 수 있다.
구성 요소 | 역할 | 데이터 흐름 방향 |
|---|---|---|
액션 | 상태를 변경하려는 의도를 나타내는 객체. | → 디스패처 |
디스패처 | 모든 액션을 중앙에서 받아 스토어에 전달하는 허브. | → 스토어 |
스토어 | 애플리케이션의 상태와 비즈니스 로직을 보유. 상태 변경 후 뷰에 알림. | → 뷰 |
뷰 | 스토어의 상태를 반영하여 사용자 인터페이스를 렌더링. 사용자 입력 시 새로운 액션 생성. | → (새로운) 액션 |
이 표에서 볼 수 있듯, 데이터는 뷰에서 다시 액션으로 흐를 수 있지만, 이는 새로운 사이클의 시작을 의미할 뿐 기존 흐름을 역행하는 것이 아니다. 이러한 엄격한 규칙은 애플리케이션의 동작을 더욱 결정론적으로 만든다.
액션(Action)은 애플리케이션에서 발생하는 모든 상태 변화를 설명하는 객체이다. 액션은 반드시 어떤 종류의 변화인지를 나타내는 type 필드를 가지며, 해당 변화에 필요한 추가 데이터를 담은 payload 필드를 가질 수 있다. 예를 들어, "TODO_ITEM_ADD"라는 타입과 새 할 일 항목의 텍스트를 페이로드로 담을 수 있다. 액션은 상태를 직접 변경하지 않으며, 단지 "무엇이 일어났는지"에 대한 정보를 담은 평범한 객체일 뿐이다.
액션은 디스패처(Dispatcher)라는 중앙 허브를 통해 전달된다. 디스패처는 모든 데이터 흐름을 관리하는 단일 지점이다. 액션이 생성되면 디스패처로 보내지며, 디스패처는 등록된 모든 스토어(Store)에 이 액션을 전파한다. 이는 MVC 패턴에서 모델과 뷰가 서로 직접 통신하거나 이벤트를 방출하는 복잡한 구조와 달리, 상태 변화의 경로를 명확히 하고 순서를 보장하는 역할을 한다.
액션을 수신한 스토어는 자신이 관리하는 상태의 변경이 필요한지 액션 타입을 확인한다. 필요한 변경이라면, 스토어 내부의 로직에 따라 상태를 업데이트한다. 스토어는 애플리케이션의 특정 도메인(예: 사용자 데이터, 상품 목록)에 대한 상태와 그 상태를 변경하는 비즈니스 로직을 캡슐화한다. 상태가 변경되면 스토어는 자신에게 등록된 뷰(View)에게 변경 사실을 알린다.
뷰(일반적으로 UI 컴포넌트)는 스토어로부터 상태 변경 알림을 받으면, 새로운 상태를 가져와 자신을 다시 렌더링한다. 뷰는 사용자 입력에 반응하여 새로운 액션을 생성하고 디스패처로 보낼 수도 있다. 이렇게 액션 → 디스패처 → 스토어 → 뷰로 이어지는 흐름은 항상 단방향으로만 진행되며, 이 순환 구조를 단방향 데이터 흐름이라고 부른다.
구성 요소 | 역할 | 특징 |
|---|---|---|
상태 변화를 설명하는 정보 객체 |
| |
액션을 모든 스토어에 전파하는 중앙 허브 | 액션 발송 순서 동기적으로 관리 | |
애플리케이션 상태와 비즈니스 로직 보유 | 상태 변경 후 뷰에 변경 알림 | |
상태를 화면에 표시하는 UI 계층 | 스토어를 구독하고, 사용자 입력 시 액션 생성 |

Flux 패턴의 개념을 구현한 주요 라이브러리로는 Redux, MobX, Vuex, 그리고 Recoil과 Jotai 등이 있다. 각 구현체는 단방향 데이터 흐름이라는 핵심 원리를 공유하지만, 상태를 정의하고 변경하는 방식, 그리고 학습 곡선과 사용 편의성에서 뚜렷한 차이를 보인다.
가장 정통적인 Flux 구현체로 꼽히는 Redux는 명시적이고 엄격한 규칙을 강조한다. 상태는 불변 객체로 관리되며, 순수 함수인 리듀서를 통해서만 상태 변경이 허용된다. 이는 상태 변화를 매우 예측 가능하게 만들지만, 비교적 많은 보일러플레이트 코드를 요구한다는 특징이 있다. 반면, MobX는 관찰 가능한 상태와 반응형 프로그래밍 패러다임을 채택한다. 상태를 직접 변형하는 것이 허용되며, 자동으로 관련된 뷰를 업데이트하는 방식으로, 객체 지향적이고 직관적인 API를 제공한다.
Vue 생태계의 공식 상태 관리 라이브러리인 Vuex는 Vue 애플리케이션에 특화된 구조를 가진다. Redux와 유사하게 액션, 변이, 상태의 개념을 사용하지만, Vue의 반응성 시스템과 긴밀하게 통합되어 있다. 상태는 반응형으로 추적되며, 컴포넌트에서 편리하게 사용할 수 있는 맵핑 헬퍼를 제공한다. 최근 등장한 Recoil과 Jotai는 원자적 상태 관리 모델을 제시한다. 애플리케이션 상태를 작은 단위의 원자로 분해하고, 이를 조합하여 파생 상태를 만드는 방식으로, 복잡한 상태 의존성을 효율적으로 관리하고 코드 분할과의 호환성을 높이는 데 중점을 둔다.
구현체 | 주요 특징 | 상태 변경 방식 | 주로 사용되는 생태계 |
|---|---|---|---|
엄격한 단방향 흐름, 불변성, 미들웨어 | 순수 리듀서 함수를 통한 명시적 변경 | ||
관찰 가능 상태, 반응형 프로그래밍, 간결함 | 상태 객체의 직접 변형 허용 | React | |
Vue 공식 라이브러리, 반응성 시스템 통합 | 액션 -> 변이를 통한 변경 | ||
원자적 상태 관리, 파생 상태, 비동기 지원 | 원자(Atom) 단위의 상태 설정 함수 | React |
Redux는 Flux 패턴을 기반으로 한 가장 대표적인 JavaScript 상태 관리 라이브러리이다. Dan Abramov와 Andrew Clark에 의해 2015년에 만들어졌으며, 특히 React 생태계와의 강력한 결합으로 널리 채택되었다. Redux는 Flux의 핵심 개념을 유지하면서도 몇 가지 엄격한 원칙을 추가하여 애플리케이션의 상태 변화를 더욱 예측 가능하고 추적 가능하게 만드는 것을 목표로 한다.
Redux의 핵심은 세 가지 원칙으로 요약된다. 첫째, 애플리케이션의 전체 상태는 하나의 JavaScript 객체인 단일 스토어(Store)에 저장된다. 둘째, 상태는 읽기 전용이며, 오직 액션(Action) 객체를 통해서만 변경될 수 있다. 셋째, 상태 변경은 순수 함수인 리듀서(Reducer)를 통해 이루어진다. 리듀서는 이전 상태와 액션 객체를 받아 다음 상태를 반환하는 함수로, 동일한 입력에 대해 항상 동일한 출력을 보장해야 한다. 이 구조는 디버깅, 상태 스냅샷 저장 및 재현, 시간 여행 디버깅과 같은 고급 기능을 가능하게 한다.
Redux의 일반적인 데이터 흐름은 다음과 같다. 뷰(예: React 컴포넌트)는 사용자 인터랙션에 응답하여 특정 타입을 가진 액션을 디스패치(발행)한다. 이 액션은 미들웨어를 거쳐 리듀서로 전달된다. 리듀서는 현재 상태와 액션을 처리하여 완전히 새로운 상태 객체를 생성한다. 최종적으로 스토어의 상태가 업데이트되면, 스토어에 구독(subscribe)하고 있던 뷰들이 새로운 상태를 받아 리렌더링된다. 이러한 엄격한 단방향 흐름은 상태 변화의 출처와 경로를 명확히 한다.
특징 | 설명 |
|---|---|
단일 진실 공급원(Single Source of Truth) | 애플리케이션의 모든 상태가 하나의 스토어 트리 구조에 저장된다. |
상태는 읽기 전용(State is Read-Only) | 상태를 직접 수정할 수 없으며, 액션을 발생시켜야만 변경할 수 있다. |
변화는 순수 함수로 작성(Changes are Made with Pure Functions) | 리듀서는 이전 상태와 액션을 받아 부수 효과 없이 새로운 상태를 반환하는 순수 함수여야 한다. |
미들웨어(Middleware) | 액션이 리듀서에 도달하기 전에 비동기 작업, 로깅, 사이드 이펙트 처리를 위한 확장 메커니즘을 제공한다[2]. |
Redux는 높은 예측 가능성과 강력한 개발자 도구 지원으로 대규모 애플리케이션에서 강점을 보이지만, 상대적으로 많은 보일러플레이트 코드를 요구한다는 비판도 받는다. 이에 따라 Redux Toolkit과 같은 공식 권장 도구 키트가 등장하여 설정과 코드 작성을 간소화하였다.
MobX는 Flux 패턴의 구현체 중 하나로, 반응형 프로그래밍 원리를 기반으로 한 상태 관리 라이브러리이다. Redux가 명시적이고 엄격한 단방향 데이터 흐름을 강조한다면, MobX는 관찰 가능한 상태(Observable State)를 정의하고 이 상태의 변화가 자동으로 파생된 값(Computed Value)과 반응(Reaction)을 일으키도록 하는 암시적이고 자동화된 접근 방식을 취한다. 이는 개발자가 상태 변경 로직에 집중하는 동안, 시스템이 데이터의 일관성을 자동으로 유지하도록 한다.
MobX의 핵심 개념은 관찰 가능한 상태(Observable), 상태를 변경하는 액션(Action), 상태로부터 자동으로 계산되는 값(Computed), 그리고 상태 변화에 반응하는 부수 효과(Reaction)로 구성된다. 개발자는 클래스의 프로퍼티를 observable로 데코레이팅하여 관찰 대상으로 만들고, 상태를 수정하는 메서드를 action으로 표시한다. computed 데코레이터를 사용하면 다른 관찰 가능한 상태들로부터 자동으로 계산되는 값을 정의할 수 있으며, autorun이나 reaction 같은 함수를 사용하여 상태 변화 시 UI 렌더링이나 다른 로직이 실행되도록 설정한다.
개념 | 역할 | 예시 |
|---|---|---|
관찰 대상이 되는 상태 |
| |
상태를 변경하는 함수 |
| |
상태로부터 파생된 값 |
| |
상태 변화에 대한 반응(부수 효과) |
|
이러한 구조 덕분에 MobX는 비교적 적은 보일러플레이트 코드로 복잡한 상태 의존성을 관리할 수 있다. 상태가 변경되면 이에 의존하는 모든 computed 값과 reaction이 자동으로, 그리고 최적화된 방식으로 갱신된다. 이는 객체 지향 프로그래밍 스타일과 잘 어울리며, 특히 대규모 애플리케이션에서 세밀한 관찰 구독을 통해 불필요한 렌더링을 줄이고 성능을 최적화하는 데 유리하다. 그러나 상태 변화의 흐름이 암시적이기 때문에, 디버깅 시 특정 변화의 출처를 추적하는 것이 Redux에 비해 다소 어려울 수 있다는 점이 지적된다.
Vuex는 Vue.js 애플리케이션을 위한 공식 상태 관리 라이브러리이자 Flux 패턴 구현체이다. Vuex는 애플리케이션의 모든 컴포넌트가 공유하는 중앙 집중식 저장소(Store)를 제공하여 상태를 예측 가능한 방식으로 관리한다. Vue의 반응성 시스템과 깊게 통합되어 있어, 상태가 변경되면 이를 의존하는 Vue 컴포넌트가 자동으로 업데이트된다.
Vuex의 핵심 구조는 State, Getters, Mutations, Actions 네 가지로 구성된다. State는 애플리케이션의 상태 데이터 자체를 의미하는 단일 객체이다. Getters는 저장소의 상태를 계산하는 함수로, 컴포넌트의 계산된 속성(computed property)과 유사한 역할을 한다. 상태를 변경하는 유일한 방법은 동기적인 Mutations를 커밋하는 것이다. 비동기 로직이나 복잡한 연산은 Actions에서 처리하며, 액션은 최종적으로 변이를 커밋하여 상태를 변경한다.
개념 | 역할 | 특징 |
|---|---|---|
State | 애플리케이션의 단일 상태 트리 | 반응형 데이터, 직접 변경 불가 |
Getters | 상태에서 파생된 값 계산 | 저장소의 계산된 속성 |
Mutations | 상태를 변경하는 유일한 방법 | 동기적 함수, |
Actions | 비즈니스 로직 처리 | 비동기 작업 가능, |
Vuex는 특히 중대형 규모의 단일 페이지 애플리케이션(SPA)에서 컴포넌트 간 상태 공유와 복잡한 상태 변화를 체계적으로 관리하는 데 유용하다. 그러나 Vue 3와 Composition API의 등장 이후, 더 가볍고 유연한 대안인 Pinia가 공식적으로 권장되는 새로운 상태 관리 라이브러리로 부상했다[3]. Pinia는 Vuex와 유사한 개념을 가지면서도 더 간결한 API와 TypeScript 지원을 강화했다.
Recoil은 Meta(구 Facebook)에서 개발한 React 전용 상태 관리 라이브러리이다. 핵심 개념은 Atom과 Selector로 구성된다. Atom은 상태의 단위이며, 컴포넌트는 Atom을 구독하여 상태가 변경되면 자동으로 리렌더링된다. Selector는 순수 함수로, 하나 이상의 Atom 상태를 입력받아 파생된 상태를 계산하여 제공한다. 이 구조는 React의 훅(useRecoilState, useRecoilValue)과 자연스럽게 통합되어, 컴포넌트 내에서 선언적으로 상태를 읽고 쓸 수 있게 한다.
Jotai는 Recoil의 원자적(Atomic) 개념에 영감을 받아 만들어진 간결한 상태 관리 라이브러리이다. Recoil과 마찬가지로 원자(atom)를 기본 단위로 사용하지만, API 설계 철학에서 차이를 보인다. Jotai는 더 작은 번들 사이즈와 최소한의 보일러플레이트를 지향하며, Context와 유사한 방식으로 원자를 생성하고 사용한다. Recoil의 Selector에 해당하는 비동기 처리나 파생 상태는 Jotai에서 atom 함수 자체의 옵션을 통해 구현된다.
두 라이브러리는 기존 Flux 패턴 기반의 중앙 집중식 스토어(Store)와 대비되는 특징을 가진다. 주요 특징을 비교하면 다음과 같다.
특징 | Recoil | Jotai |
|---|---|---|
개발사/커뮤니티 | Meta 주도 | 개인 개발자 주도, 활발한 커뮤니티 |
핵심 개념 | Atom, Selector | Atom (Primitive) |
비동기 처리 | Selector를 통한 비동기 쿼리 지원 |
|
번들 크기 | 상대적으로 큼 | 매우 작음 |
API 복잡도 | 상대적으로 높음 (RecoilRoot, 키 관리 등) | 매우 낮음 (React Context 스타일) |
둘 모두 애플리케이션의 상태를 작은 단위로 분리하고, 필요에 따라 효율적으로 구독 및 업데이트할 수 있도록 하는 원자적 상태 관리(Atomic State Management)의 대표적인 예다. 이 방식은 거대한 단일 스토어를 관리하는 부담을 줄이고, 코드 분할과 성능 최적화에 유리하다. 선택은 프로젝트의 규모, 팀의 익숙함, 비동기 상태 처리의 필요성 등에 따라 달라진다.

Flux 패턴의 가장 큰 장점은 단방향 데이터 흐름에 의해 상태 변화가 매우 예측 가능해진다는 점이다. 모든 상태 변경은 액션을 통해 명시적으로 시작되고, 디스패처를 거쳐 스토어에서 처리된 후 뷰에 반영된다. 이 구조는 데이터가 한 방향으로만 흐르도록 강제함으로써, 상태가 언제, 어디서, 왜 변경되는지를 명확히 추적할 수 있게 한다. 특히 복잡한 사용자 상호작용이 많은 대규모 애플리케이션에서, 여러 컴포넌트가 동일한 상태를 공유하거나 변경할 때 발생할 수 있는 사이드 이펙트와 경쟁 상태를 효과적으로 방지한다. 이는 디버깅과 테스트를 용이하게 하고, 애플리케이션의 동작을 이해하기 쉽게 만든다.
그러나 이러한 엄격한 구조는 동시에 단점으로 작용하기도 한다. Flux 패턴을 구현하는 라이브러리들은 비교적 많은 양의 보일러플레이트 코드를 요구한다. 상태를 변경하기 위해서는 액션 타입을 정의하고, 액션 생성 함수를 만들며, 디스패처를 통해 액션을 발행하고, 스토어에서 해당 액션을 처리하는 리듀서 로직을 작성해야 한다. 이는 간단한 상태 업데이트에도 여러 파일을 오가며 코드를 작성해야 하는 번거로움을 초래한다. 결과적으로 학습 곡선이 가파르고, 소규모 프로젝트에서는 오히려 과도한 설계로 느껴질 수 있다.
장점 | 단점 |
|---|---|
상태 변화의 예측 가능성과 추적성 향상 | 비교적 많은 보일러플레이트 코드 필요 |
데이터 흐름의 명확성과 디버깅 용이성 | 학습 곡선이 가파름 |
복잡한 애플리케이션에서의 사이드 이펙트 관리 용이 | 소규모 또는 간단한 프로젝트에는 과할 수 있음 |
컴포넌트 간 명확한 책임 분리 촉진 | 상태 업데이트를 위한 코드 작성 절차가 다소 장황함 |
이러한 장단점은 Flux 패턴을 기반으로 한 다양한 구현체들에서 각기 다른 방식으로 해결되거나 강조된다. 예를 들어, Redux는 단일 스토어와 불변성에 집중하여 예측 가능성을 극대화하지만 보일러플레이트 문제가 지적되는 반면, MobX는 관찰 가능한 상태를 사용하여 보일러플레이트를 크게 줄이지만 마법 같은 자동 추적으로 인해 내부 동작을 이해하기 어려울 수 있다. 따라서 프로젝트의 규모, 복잡도, 개발 팀의 선호도에 따라 적절한 구현체를 선택하는 것이 중요하다.
Flux 패턴의 가장 큰 장점은 상태(State) 변화를 매우 예측 가능하게 만든다는 점이다. 이는 단방향 데이터 흐름이라는 철학에서 비롯된다. 모든 데이터 흐름이 액션 → 디스패처 → 스토어 → 뷰라는 하나의 방향으로만 진행되기 때문에, 상태가 언제, 어디서, 왜 변경되는지 추적하기 쉬워진다.
이러한 예측 가능성은 특히 대규모 애플리케이션에서 빛을 발한다. 복잡한 사용자 상호작용이 여러 컴포넌트에 걸쳐 상태를 변경시킬 때, 변경의 출발점은 항상 명시적인 액션(Action)이다. 개발자는 액션의 종류와 발생 순서만으로 애플리케이션의 상태 변화 흐름을 이해할 수 있으며, 버그가 발생했을 때도 액션 로그를 역추적하여 문제의 근본 원인을 찾아내는 데 유리하다.
비교 요소 | 전통적 양방향 바인딩 / MVC | Flux 패턴 |
|---|---|---|
데이터 흐름 | 양방향 또는 다방향 | 엄격한 단방향 |
변화 추적 | 어려움 (변화가 여러 곳에서 동시 발생) | 용이함 (액션을 통한 유일한 진입점) |
디버깅 | 복잡함 (의존성 추적 난해) | 상대적 용이함 (액션 로그 재생 가능) |
결과적으로, 애플리케이션의 동작이 결정론적(deterministic)이 되어, 동일한 액션 시퀀스는 항상 동일한 최종 상태를 보장한다. 이는 테스트 작성과 상태의 시간 여행(Time-travel debugging) 같은 고급 디버깅 기법을 가능하게 하는 기반이 된다[4].
Flux 패턴의 구조적 엄격성은 예측 가능성을 높이는 대신, 비교적 많은 양의 보일러플레이트 코드를 요구한다는 단점을 동반한다. 특히 Redux와 같은 엄격한 구현체에서는 하나의 상태 변경을 위해 액션(Action) 타입 상수, 액션 생성자(Action Creator), 리듀서(Reducer) 함수를 모두 작성해야 한다. 이 과정은 간단한 업데이트에도 여러 파일을 오가며 코드를 작성하게 만든다. 또한, 스토어(Store)의 상태가 불변성을 유지해야 하기 때문에, 깊은 중첩 구조의 객체를 업데이트할 때는 스프레드 연산자나 이머(Immer) 같은 라이브러리를 활용한 복잡한 갱신 로직이 필요해진다.
이러한 복잡성은 소규모 애플리케이션에서는 과도한 부담으로 작용할 수 있다. 프로젝트 초기에는 간단한 컴포넌트(Component)의 지역 상태로 충분히 관리할 수 있는 기능도, Flux 아키텍처를 따르기 위해 디스패처(Dispatcher)를 거치는 긴 데이터 흐름을 설계해야 한다. 아래 표는 전통적인 MVC 패턴의 양방향 바인딩과 Flux의 단방향 흐름을 코드 복잡도 측면에서 비교한다.
비교 요소 | 전통적 MVC / 양방향 바인딩 | Flux 패턴 (예: Redux) |
|---|---|---|
상태 변경 트리거 | 뷰에서 직접 모델 수정 | 액션 생성 및 디스패치 |
데이터 흐름 | 양방향, 종속성 추적 어려움 | 엄격한 단방향 (액션 → 디스패처 → 스토어 → 뷰) |
코드 양 | 일반적으로 적음 | 보일러플레이트 코드 다수 필요[5] |
디버깅 용이성 | 변화의 출처 추적 어려움 | 액션 로그를 통한 명확한 상태 변화 히스토리 |
결과적으로, 개발자는 애플리케이션의 복잡도가 충분히 높지 않은 상황에서도 패턴을 준수하기 위한 구조적 코드에 상당한 시간을 투자해야 한다. 이는 학습 곡선을 가파르게 만들고, 신규 팀원의 프로젝트 진입 장벽을 높이는 요인이 된다. 따라서 프로젝트의 규모와 복잡도를 정확히 평가한 후, Flux 패턴의 도입 여부를 결정하는 것이 중요하다.

상태 관리의 진화는 단일 페이지 애플리케이션의 복잡성 증가와 함께 지속적으로 새로운 접근법을 낳았다. 기존의 Flux 패턴이나 전역 상태 관리에 집중하던 방식에서 벗어나, 상태의 종류와 특성에 따라 적합한 도구를 선택하는 세분화된 접근이 주류를 이루고 있다. 특히 클라이언트 상태와 서버 상태를 명확히 분리하여 관리하는 패러다임이 강조된다.
서버 상태 관리 라이브러리인 React Query와 SWR은 이러한 흐름의 대표적인 예시이다. 이들은 데이터 페칭, 캐싱, 동기화, 에러 처리 등 비동기 서버 상태의 복잡한 부수 효과들을 추상화한다. 개발자는 useState와 useEffect를 조합해 직접 구현해야 했던 수많은 로직(로딩 상태, 폴링, 윈도우 포커스 시 재검증 등)을 선언적으로 처리할 수 있게 되었다. 이는 클라이언트 상태 관리 도구의 역할을 순수한 UI 상태나 폼 상태 등으로 한정시키는 효과를 가져왔다.
또 다른 주요 동향은 원자적 상태 관리이다. Recoil과 Jotai는 작은 단위의 상태(원자)를 정의하고, 이들을 조합(셀렉터)하여 파생 상태를 만드는 방식을 채택한다. 이는 거대한 스토어 객체를 관리하는 Redux의 방식과 대비되며, 코드 분할과 동적 구독을 용이하게 한다. 동시에, 상태 머신 라이브러리인 XState는 상태를 유한한 집합으로 명시적으로 모델링하고, 상태 간 전이를 시각적이고 예측 가능하게 제어하는 방식을 제시한다. 이는 복잡한 UI 흐름(예: 결제 단계, 다단계 폼)의 로직을 시각적 상태 차이어트로 표현해 유지보수성을 높인다.
라이브러리/패턴 | 핵심 개념 | 주요 해결 문제 |
|---|---|---|
서버 상태 캐싱 및 동기화 | 데이터 페칭의 복잡성, 중복 요청, 캐시 무효화 | |
원자적 상태와 파생 상태 | 거대한 전역 스토어의 불필요한 리렌더링, 코드 분할의 어려움 | |
유한 상태 머신 | 복잡한 비즈니스 로직과 상태 전이의 명시적 모델링 |
이러한 도구들은 상호 배타적이지 않으며, 프로젝트의 요구사항에 따라 조합되어 사용된다. 예를 들어, React Query로 서버 상태를, Jotai로 클라이언트 UI 상태를, XState로 특정 컴포넌트의 복잡한 로직을 관리하는 식이다. 상태 관리의 최신 동향은 '만능 도구'를 찾기보다는, '적절한 도구를 적절한 곳에' 사용하는 실용주의와 관심사 분리 원칙을 잘 보여준다.
클라이언트 애플리케이션에서 관리하는 UI 상태와 달리, 서버 상태는 데이터베이스에 저장되어 API를 통해 가져오는 데이터를 의미한다. 이는 비동기적이며, 공유되고, 지속적으로 변경될 수 있는 특성을 가진다. 전통적인 Flux 패턴 기반의 상태 관리 라이브러리는 이러한 서버 상태의 캐싱, 동기화, 에러 처리, 무효화 등의 복잡한 문제를 해결하는 데 한계가 있었다.
이러한 문제를 해결하기 위해 등장한 도구들이 React Query와 SWR이다. 이들은 서버 상태를 위한 전용 라이브러리로, 데이터 페칭 로직을 단순화하고 강력한 캐싱 전략을 제공한다. 주요 기능으로는 자동 재시도, 백그라운드 리페칭, 낙관적 업데이트, 페이지네이션 및 무한 스크롤 지원 등이 있다. 이를 통해 개발자는 데이터를 "가져오는" 방법에 집중할 수 있고, 데이터의 "상태"는 라이브러리가 자동으로 관리한다.
특성 | React Query | SWR |
|---|---|---|
주요 특징 | 강력한 캐싱, 무효화, 병렬 쿼리 | 경량화, 빠른 통합, Stale-While-Revalidate 전략 |
개발 주체 | Tanner Linsley | Vercel |
상태 갱신 방식 | 명시적 무효화 및 백그라운드 리페치 | 포커스/네트워크 재연결 시 자동 리페치 |
번들 크기 | 상대적으로 큼 | 상대적으로 작음 |
이러한 도구들은 클라이언트 상태 관리 라이브러리(Redux, Zustand 등)를 완전히 대체하기보다는 보완하는 역할을 한다. 애플리케이션은 React Query나 SWR으로 서버 상태를 관리하고, UI 상태는 기존의 상태 관리 도구로 처리하는 하이브리드 방식이 일반적이다. 이는 관심사의 분리를 명확히 하고, 각 도구가 가장 잘하는 일에 집중하게 한다.
원자적 상태 관리(Atomic State Management)는 애플리케이션의 상태를 가능한 한 작고 독립적인 단위, 즉 '원자(Atom)'로 분해하여 관리하는 접근 방식이다. 이 패러다임은 React 생태계에서 Recoil과 Jotai 같은 라이브러리를 통해 주목받았다. 전통적인 Flux 패턴이 하나의 중앙 집중식 스토어(Store)를 사용하는 것과 달리, 원자적 상태 관리는 수많은 작은 상태 조각들을 분산시켜 구성한다. 각 원자는 애플리케이션 내 어디서든 구독하고 업데이트할 수 있는 독립적인 상태 단위가 된다.
이 방식의 핵심 장점은 상태의 세분화와 파생 상태의 효율적 관리에 있다. 개발자는 애플리케이션 전체를 위한 거대한 상태 트리를 설계하기보다, 필요한 최소한의 상태만을 원자로 정의할 수 있다. 또한, 다른 원자들로부터 계산되어 생성되는 파생 상태(Derived State)를 쉽게 선언할 수 있어, 상태 간의 의존성을 명시적이고 반응적으로 관리할 수 있다. 이는 컴포넌트의 재렌더링을 해당 컴포넌트가 실제로 구독하는 원자 상태의 변경에만 국한시켜 성능을 최적화하는 데 기여한다.
주요 라이브러리 간의 특징은 다음과 같이 비교할 수 있다.
라이브러리 | 주요 특징 | API 스타일 |
|---|---|---|
Facebook(Meta)에서 개발. | 명시적(Hooks 기반) | |
간결함과 유연성을 중시. 원자 개념에 집중하며, Recoil의 영감을 받았지만 더 작은 번들 크기를 지향함. | 유연성(프리미티브 기반) |
원자적 상태 관리는 중소 규모의 애플리케이션이나 Flux 패턴의 보일러플레이트 코드가 과도하게 느껴지는 프로젝트에 적합한 대안으로 평가받는다. 그러나 상태 조각이 너무 분산되면 상태 변화의 흐름을 추적하기 어려워질 수 있다는 점과, 아직까지는 Redux와 같은 기존 솔루션에 비해 생태계와 도구 지원이 상대적으로 작다는 점은 고려해야 할 요소이다.
상태 머신은 유한 상태 기계 개념을 소프트웨어 상태 관리에 적용한 패턴이다. 이 접근법은 애플리케이션의 상태를 '유한한 개수의 명확한 상태'와 그 상태 간의 '전이'로 정의한다. XState는 자바스크립트와 타입스크립트를 위한 상태 머신 및 상태차트 라이브러리로, 복잡한 상태 로직을 시각적이고 선언적으로 모델링할 수 있게 해준다.
XState의 핵심 구성 요소는 상태, 전이, 이벤트, 액션이다. 개발자는 머신을 정의하여 애플리케이션이 가질 수 있는 모든 상태(예: 'idle', 'loading', 'success', 'error')와 특정 이벤트가 발생했을 때 어떤 상태로 전이되는지를 명시한다. 각 전이에는 부수 효과로 실행될 액션을 연결할 수 있다. 이는 상태 변화와 부수 효과를 명시적으로 분리하여 로직을 이해하고 테스트하기 쉽게 만든다.
구성 요소 | 설명 |
|---|---|
상태(State) | 시스템이 가질 수 있는 유한한 상태 (예: |
이벤트(Event) | 상태 변화를 트리거하는 입력 또는 사건 |
전이(Transition) | 특정 이벤트에 의해 한 상태에서 다른 상태로 이동하는 규칙 |
액션(Action) | 전이 시 실행되는 부수 효과 (예: API 호출, 로깅) |
컨텍스트(Context) | 상태 머신과 관련된 확장된 데이터(상태가 아닌) |
전통적인 Flux 패턴이나 원자적 상태 관리와 비교했을 때, XState의 주요 장점은 상태 로직의 명시성과 시각화 가능성이다. 복잡한 분기 로직(if-else 문)이나 동시에 발생할 수 있는 상태를 상태차트로 명확하게 표현할 수 있다. 또한, 상태 머신은 항상 유효한 상태만을 보장하도록 설계되므로, 불가능한 상태에 도달하는 버그를 방지하는 데 도움을 준다. 반면, 학습 곡선이 존재하며 비교적 많은 보일러플레이트 코드가 필요할 수 있다는 점이 단점으로 지적된다.

프로젝트에 적합한 상태 관리 도구를 선택하는 것은 애플리케이션의 규모, 복잡도, 팀의 숙련도, 사용 중인 프레임워크 등 여러 요소를 종합적으로 고려해야 하는 결정이다. 올바른 선택은 개발 생산성과 유지보수성을 크게 향상시킨다.
선택 시 주요 고려 사항은 다음과 같다.
고려 요소 | 설명 및 질문 |
|---|---|
프로젝트 규모와 복잡도 | 소규모 프로젝트는 React의 useState, useReducer 또는 Vue의 반응형 시스템으로 충분할 수 있다. 대규모 엔터프라이즈 애플리케이션은 Redux나 Zustand와 같은 중앙 집중식 상태 관리가 적합하다. |
학습 곡선과 보일러플레이트 | Redux는 강력한 구조와 예측 가능성을 제공하지만, 설정과 코드량이 많다. MobX나 Jotai는 상대적으로 간결한 API를 제공한다. |
프레임워크/라이브러리 | Vue 프로젝트에는 Vuex(Pinia)가, Angular에는 NgRx나 Akita가 자연스럽게 통합된다. React 생태계는 선택지가 가장 다양하다. |
데이터 흐름 패턴 | 엄격한 단방향 데이터 흐름이 필요한지, 아니면 양방향 바인딩이나 관찰 가능한 객체를 선호하는지 고려한다. |
비동기 처리 요구사항 | 복잡한 비동기 로직(예: 데이터 페칭)이 많다면 Redux Toolkit의 createAsyncThunk, Redux-Saga, 또는 전용 서버 상태 관리 라이브러리를 함께 고려한다. |
팀의 선호도와 전문성 | 팀원들이 함수형 프로그래밍에 익숙한지, 객체 지향적 접근을 선호하는지, 새로운 개념을 수용할 수 있는지 평가한다. |
일반적인 시나리오에 따른 선택 가이드는 다음과 같다. 소규모 또는 중간 규모의 React 애플리케이션에는 Context API와 useReducer의 조합이나 Zustand가 경량화된 솔루션으로 권장된다. 복잡한 비즈니스 로직과 엄격한 상태 추적이 필요한 대규모 애플리케이션에는 Redux Toolkit이 여전히 강력한 옵션이다. 반응형 프로그래밍 스타일을 선호하고 객체 지향적인 방식을 원한다면 MobX가 적합하다. Vue 3 프로젝트에서는 공식 권장 사항인 Pinia를 사용하는 것이 표준이다. 원자적 상태 관리에 관심이 있고 최소한의 보일러플레이트를 원한다면 Jotai나 Recoil을 검토할 수 있다. 마지막으로, 서버에서 오는 데이터(캐시, 동기화, 페이징 등)의 상태를 관리하는 것이 주요 고민이라면 React Query(TanStack Query)나 SWR과 같은 서버 상태 관리 라이브러리를 도입하는 것이 근본적인 해결책이 될 수 있다.
결론적으로, 만능 해결책은 존재하지 않는다. 프로젝트 초기에는 가장 단순한 방법으로 시작하고, 필요에 따라 점진적으로 더 구조화된 도구로 전환하는 접근법이 효과적이다. 중요한 것은 선택한 도구가 프로젝트의 요구사항을 해결하는 도구일 뿐, 그 자체가 목적이 되어서는 안 된다는 점이다.
