-
[Pattern] One-way Data Flow : Flux, Redux, ReactFront-End/ETC 2025. 2. 7. 17:16
🍀목차
글의 목적
cf. Two-way Data Flow
Flux, Redux, React
느낀 점글의 목적
1년 전, React의 상태 관리에 관한 글을 작성했다. https://youngju-js.tistory.com/42
[React] 상태 관리 (+라이브러리)
🍀 목차전역 상태상태 관리의 필요성대표적 라이브러리와 콘셉트계속되는 연구들 전역 상태 상태는 변화하는 동적인 데이터이다. 웹 페이지에서 데이터는 사용자와 상호작용을 통해 변경
youngju-js.tistory.com
상태 관리의 필요성과 관련 라이브러리들을 살펴보면서, Redux의 탄생에 큰 영감을 준 Flux 아키텍처도 가볍게 알아보았다.
이번 글에서는 Flux 아키텍처의 핵심인 One-way (direction) Data Flow (Unidirectional Data Flow, 단방향 데이터 흐름)에 초점을 맞춰보려 한다. 이 패턴이 복잡한 시스템에서 어떤 이점을 주는지 살펴보고, Flux 아키텍처와 Redux, React는 어떻게 적용했는지 이해해 보자.
cf. Two-way Data Flow
Two-way (direction) Data Flow(Bidirectional Data Flow, 양방향 데이터 흐름)는 소프트웨어 디자인 패턴의 요소인 Model(데이터, 비즈니스 로직 담당)과 View(UI, 화면을 처리하고 입력은 전달) 사이를 데이터가 순환할 수 있는 패턴이다.프레임워크 차원에서 해당 패턴을 구현하기 위한 방법으로 양방향 데이터 바인딩(Two-way Data Binding)을 지원한다. Vue의 v-model이나 Angular의 NgModel의 경우, 개발자가 별도의 동기화 로직을 작성하지 않아도 UI와 모델 간의 자동 동기화가 이루어지는 기능을 제공한다.
📌 바인딩(Binding) - 출처
프로그래밍 관점에서, 바인딩은 값과 식별자(변수, 함수, 매개변수, this 등) 사이의 연관 관계를 의미한다. 어떤 값이 특정 이름이나 컨텍스트에 연결되는 것을 뜻한다. 일부 바인딩은 언어에 의해 암시적으로 생성된다. 예를 들어, JavaScript의 this를 생각해 볼 수 있는데, this는 호출되는 방식에 따라 값이 달라진다.이러한 기능은 요구사항에 따라 이점이 존재한다.
React의
Controlled Component(제어 컴포넌트)
와 비교하자면, 제어 컴포넌트는 값을 제어(동기화)하기 위해 state 변경을 관리할 이벤트 핸들러 로직 작성이 필요하다. 그에 비해 양방향 데이터 바인딩은 Form element를 다룰 때 별도의 이벤트 핸들러를 작성할 필요 없이 데이터와 사용자 입력이 자동으로 연결되므로 코드량이 줄어든다.다만, 복잡한 상태 관리가 필요한 대규모 애플리케이션에는 자동 동기화가 오히려 데이터 흐름 파악을 해칠 수 있다. 그리고 동기화를 위한 추가적인 메커니즘(Watcher, Observer 등)이 필요한데 Watcher가 반복적으로 상태 변화를 감지하는 과정에서 바인딩된 데이터가 많을 경우 성능 저하가 일어날 수 있다.
각 프레임워크들이 다양한 최적화 기법을 도입했지만 동기화는 그에 따른 추적 비용이 있다는 점을 고려해야 한다.
이를 보완하기 위해 추론과 디버깅이 쉽고, 더 명확하며, 예측 가능한 데이터 흐름을 제공하는 단방향 데이터 흐름을 선호하는 기술들이 생겨났다.
One-way (direction) Data flow(Unidirectional Data flow)는 애플리케이션의 상태(Model)를
Single Source of Truth(SSOT, 단일 진실 공급원)
으로 두고, 이 상태가 View에 반영되도록 하는 구조를 따른다.📌 Single Source of Truth(SSOT)
정보 시스템 설계 및 이론에서, 모든 데이터를 하나의 공간에서 제어하도록 조직하는 관례를 의미한다. 프로세스, 데이터베이스, 애플리케이션 등의 모든 데이터에 대해 하나의 출처를 사용하는 개념이다.데이터의 흐름이 View ➡️ Model의 방향으로는 흘러가도록 하지 않는데, View는 여러 state의 표현에 집중한 레이어이기 때문이다. 여러 곳에서 변경이 발생한다면 추적이 그만큼 어려워진다. Model ➡️ View의 흐름을 통해 Model을 애플리케이션의 단일 출처 상태 저장소로 두면, 변화를 한 곳에서 집중적으로 관리할 수 있다. 여러 추적이 용이해지는 것이다. 대규모 애플리케이션으로 갈수록 복잡성이 증가하기에 이를 관리하는 데 있어 큰 이점을 줄 수 있다.
One-way Data Flow Two-way Data Flow Model ➡️ View로 데이터가 이동 Model ↔️ View로 데이터가 이동 상태 변경의 원인과 경로를 추적하기 용이하다(예측 가능성이 높다). 데이터 변경이 여러 경로로 발생할 수 있다. 각 패턴들은 트레이드 오프(Trade-off)를 감안한 선택이라 할 수 있다. 목적에 따라서 비용과 이점을 고려한 합리적인 선택을 하면 된다.
Flux, Redux, React단방향 데이터 흐름은 다양한 아키텍처 패턴과 프로그래밍 패러다임에서도 발견할 수 있다. 대표적인 아키텍처(Flux)와 기술들을 살펴보자.
Flux
Flux의 Brif Introduction 영상 Closer Look at Flux 단방향 데이터 흐름을 활용하는 React의 컴포넌트를 보완하는 아키텍처이다.
Flux는 프레임워크가 아니기에 애플리케이션 아키텍처로서 바로 사용할 수 있다. Facebook(현 Meta)에서 Client side 웹 애플리케이션을 디자인하기 위해 만든 오픈소스 애플리케이션 아키텍처이다. (저장소 링크)
Flux의 주요 제작자는 Facebook이 몇가지 문제에 직면하고 있다고 했다. MVC 구조에 결함이 있고, 요구를 확장할 수 없다는 결론에 도달했다고 전한다. MVC는 작은 애플리케이션에는 좋지만 크고 복잡한 애플리케이션에 사용하면
fail
한다고 설명한다.Flux 아키텍처 핵심적인 구성요소는 Action, Dispatcher, Store, View다.
- Action : View에 업데이트를 발생시키는 함수. dispatcher를 통해 store의 상태를 갱신한다.
- Dispatcher : 전체 프로세스의 관리자(싱글턴, 하나만 존재해야 한다). 데이터를 Store에 전파하기위해 사용 가능한 Store에 등록된 callback을 브로드캐스트 한다. 여러 Store에 의존성이 생길 수 있는데, callback 순서를 정하는 기능을 통해 의존성 조절을 수행한다.
- Store : state에 대한 관리자. 도메인(기능) 별로 Store를 분리할 수 있다. 자신을 Dispatcher에 등록하고 callback을 제공한다. 해당 callback은 action을 파라미터로 받는다.
- View : UI에 데이터를 렌더링하고 이벤트를 처리하는 것을 담당하는 인터페이스 구성요소.
View 계층에서 사용자가 Action을 호출하면, Action은 Dispatcher로 전달되고, Dispatcher는 적절한 Store로 데이터를 전달하고, Store는 새로운 상태를 View에게 전달해 업데이트하게 만든다.
현재 Flux는 공식적으로 아카이브 처리 되었다. One-way Data Flow를 제안한 대표적인 아키텍처지만 현재는 이를 정제한 다른 라이브러리들이 많아졌기 때문이다. 그렇기에 readme에서도 다른 정교한 대안인 Redux, MobX, Recoil, Zustand, Jotai를 추천하고 있다.
https://github.com/facebookarchive/flux Redux
작성했었던 포스팅과 겹치는 부분이 있어 간단하게 정리했다.
- Flux의 중요한 특징으로부터 영감을 얻었다. 더 단순한 구조로 개선했다.
- Redux에는 Dispatcher라는 개념이 존재하지 않는다.
- Flux의 Dispatcher ➡️ Redux에서는 Reducer로 표현된다. Action 객체로만 모든 데이터 변화를 묘사하도록 강제한다.
- 순수 함수인 Reducer는 그저 이전 상태와 Action을 받아 다음 상태를 새로운 객체로 반환한다.
- 불변성이 유지되므로 디버깅이 쉬워진다.
- 여러 개의 Store를 가질 수 있는 Flux와 달리, Redux는 오직 하나의 Store만 유지한다. SSOF 개념을 더욱 강조했다.
- Flux에서 Store가 많아질 경우, Dispatcher가 각 Store에 Action을 전달하는 과정이 복잡해질 수 있다.
- Redux는 이러한 경우를 단일 Store + Reducer로 해결했다.
React
Thinking in React(https://react.dev/learn/thinking-in-react) - React는 컴포넌트들의 계층 구조가 만들어진다. 그 과정에서 자식 컴포넌트에서 부모 컴포넌트의 데이터를 재사용할 때 props를 이용하여 넘겨주게 된다. 큰 관점으로 볼 때, 최상단 컴포넌트부터 트리의 맨 아래까지 데이터가 흘러가게 되는데 이러한 흐름을 One-way Data Flow라고 부른다.
- 기본적으로 양방향 데이터 바인딩을 기능적으로 지원하지 않는다. 그러나 레거시 React에서 편의성을 위해 Two-way Binding Helpers인
LinkedStateMixin
을 지원하기도 했다(https://legacy.reactjs.org/docs/two-way-binding-helpers.html). Form의 입력 값을 다룬다던가 매번 핸들러를 작성하는 게 다소 반복적이었기에 LinkedStateMixin으로 입력 값과 state 사이의 동기화의 일부 자동화를 도왔다. 진정한 양방향 바인딩은 아니고, 상태 업데이트에 대한 간결한 헬퍼였다. 15 버전부터 사용되지 않는 것을 보면 결국 이러한 헬퍼는 임시방편이었다고 볼 수 있다.
느낀 점호기심(Why)이 많이 부족했던 것 같다.
기술을 겉햝기식으로 학습해 온 느낌이다. 기존 방식이 "어떤 상황에서, 왜" 문제가 되었는지, 그리고 변경한다면 "어떤 장점" 때문인지 정확히 이해하지 못했던 것 같다.
글을 쓰며, 기술의 근간이 되는 패턴이나 아키텍처가 어떤 여정을 거쳐 만들어졌는지 탐구하는 과정이 결국 더 깊은 고민을 (좋은 방향으로) 이끌어낸다는 생각이 들었다. 그러고 또 한 가지, 특정 패턴이나 기술이 절대적으로 "우월하다"라고 할 수 없다는 것도 느꼈다. 끊임없는 탐구를 통해 일어난 변화지, 특정 상황에서는 기존 패턴이 비용 측면에서 적합한 선택이 될 수도 있다고 생각한다.
참고자료