1. 정의
단방향 데이터 흐름(unidirectional data flow)을 강화하기 위한 패턴입니다.
2. 역할
데이터 레이어를 분리하여 데이터를 예측 가능하게 만듭니다. (React가 UI를 예측 가능하게 만든다면, Flux는 데이터를 예측 가능하게 만듭니다.)
구체적인 방법:
-
명시적인 데이터를 사용하고 파생 데이터를 사용하지 않습니다. (먼저 선언한 후 사용하며, 임시로 데이터를 생성하지 않습니다.)
-
데이터와 뷰 상태를 분리합니다. (데이터 레이어를 추출합니다.)
-
계층적 업데이트(Cascading Update)로 인한 연쇄 영향을 방지합니다. (모델과 뷰 사이의 상호 영향으로 데이터 흐름이 불투명해지는 것을 방지합니다.)
효과:
-
데이터 일관성 향상
-
버그의 정확한 위치 파악 용이
-
유닛 테스트 용이
3. 구조
Action 생성 Action 전달 State 업데이트
View 상호작용 -----------> dispatcher -----------> stores --------------> views
여기서 dispatcher는 전역에 하나만 존재하며, store는 여러 개일 수 있습니다. dispatcher는 action을 배분/전달하는 역할만 담당하며, action에서 구체적인 state 변화까지의 매핑은 store가 관리합니다. 따라서 store는 단순한 상태 집합인 model이 아니라, action에 따라 state를 업데이트하는 로직까지 포함합니다. 그다음은 state와 view의 연결이며, 이는 데이터 바인딩의 구체적인 구현과 관련이 있습니다. 예를 들어 React에서는 이벤트를 트리거하여 업데이트를 알립니다. (암시적인 setState())
비즈니스 로직의 대부분은 store에 있으며, 상호작용이나 비동기 작업과 관련된 일부 로직은 view(예: React 컴포넌트)에 있습니다.
비즈니스 로직에는 메시지 한 건을 읽음으로 처리할 때 목록의 스타일을 업데이트하고 읽지 않은 메시지 수를 줄여야 하는 것과 같은 연쇄 업데이트가 자주 발생합니다. 이러한 연쇄 업데이트는 단방향 데이터 흐름을 불투명하게 만듭니다. Flux는 반드시 최상위에서 action을 트리거하도록 제약하여 이를 방지합니다. 한 번의 view 상호작용이 한 세트의 action을 트리거합니다. (연쇄 action을 평탄화하고 그 관계를 상호작용과 직접적으로 연관된 최상위로 모읍니다.) 한 번의 view 상호작용이 하나의 큰 action을 트리거하고, 그 큰 action이 다시 하위의 연쇄 action들을 트리거하는 방식이 아닙니다.
store를 통해 제어의 역전(IoC)을 구현합니다. store는 외부에서 내부 state에 영향을 줄 수 있는 setXXX() 메서드를 제공하지 않습니다. 유일한 방법은 dispatcher에 등록된 콜백을 통해 외부 데이터를 전달받아 스스로 내부 state를 업데이트하는 것이며, 이를 통해 관심사 분리를 명확히 유지합니다.
[caption id="attachment_1422" align="alignnone" width="625"]
flux-simple-f8-diagram-explained[/caption]
단일 dispatcher
중심 허브로서 모든 데이터 흐름은 이곳을 거칩니다. 콜백 등록 테이블을 가지고 있어 각 store와 연결됩니다. dispatcher 자체는 action을 모든 store에 전달하는 역할만 수행합니다. 각 store는 dispatcher에 자신을 등록하고 콜백을 제공하며, dispatcher가 action을 받으면 등록된 모든 store가 각자의 콜백을 통해 action과 포함된 데이터를 받게 됩니다.
애플리케이션 규모가 커지면 dispatcher는 각 store 사이의 의존 관계(등록된 콜백 호출 순서)를 관리해야 하므로 다소 복잡해질 수 있습니다. store는 명시적 선언을 통해 다른 store의 업데이트가 완료될 때까지 기다렸다가 자신을 업데이트할 수 있습니다.
여러 개의 store
애플리케이션 상태와 로직을 포함하며, 역할은 *MVC의 두꺼운 모델(Fat Model)*에 해당합니다. 하지만 ORM에서 model이 데이터 레코드 하나를 나타내는 것과 달리 여러 state를 관리하며, Backbone의 collection과도 다르게 단순히 ORM 스타일의 객체 집합을 관리합니다.
하나의 store는 애플리케이션의 특정 기능에 대응하는 내부 상태를 관리합니다. 즉, store는 구체적인 데이터 모델(ORM model)이나 타입(Backbone collection)별로 나뉘는 것이 아니라 비즈니스 기능별로 구분됩니다. 예를 들어 ImageStore는 이미지 세트의 상태를, TodoStore는 할 일 목록(To-do item) 세트를 관리합니다. 이처럼 store는 데이터 측면에서는 모델 집합을, 로직 측면에서는 단일 기능을 나타냅니다.
store가 dispatcher에 등록한 콜백은 action 인자를 받습니다. store 내부는 switch 문으로 구성되어 action의 type에 따라 구체적인 state 업데이트 메서드로 분기합니다. store 업데이트가 완료되면 이벤트를 브로드캐스팅하여 view에 상태 변화를 알리고, 해당 view는 새로운 상태를 가져와 스스로를 업데이트합니다.
여러 개의 view
자신이 의존하는 store의 브로드캐스트 이벤트를 감시하는 특수한 view들을 controller-view라고 부릅니다. 이는 store에서 데이터를 가져와 하위 view로 전달하는 로직을 포함하며, 보통 페이지의 논리적 영역 하나에 대응하여 view의 논리적 그룹 역할을 합니다.
controller-view는 store로부터 이벤트를 받으면 먼저 store가 제공하는 getter를 통해 새 데이터를 가져온 뒤, 자신의 setState()나 forceUpdate()를 호출하여 render()를 실행합니다. render()가 실행되면 하위 컴포넌트들의 render()도 트리거됩니다.
보통 커다란 state 덩어리를 하위로 전달하고 하위에서 필요한 것만 골라 쓰도록 하는데, 이는 관리해야 할 상태의 수를 줄이기 위함입니다(상태를 세밀하게 쪼개지 않음). 최상위 컨트롤러가 외부에서 상태를 업데이트하는 방식과 비교할 때, 하위 컴포넌트의 기능을 최대한 순수하게 유지할 수 있습니다.
여러 개의 action
일반적으로 헬퍼 메서드를 사용하여 action 생성 및 store 등록 과정을 캡슐화하며, 내부적으로 action의 type을 통해 store와 action의 연결을 유지합니다.
action은 서버와 같은 다른 곳에서 올 수도 있습니다. 데이터 초기화 시 서버가 에러 코드를 반환하거나 서버 데이터가 업데이트되었을 때 action을 트리거하여 뷰를 동기화합니다.
4. 특징
강제 동기화
action 배분/전달과 store 내부의 state 업데이트는 모두 동기적으로 이루어집니다. 비동기 작업의 경우 작업이 완료될 때 수동으로 action을 트리거해야 하며, 전체 메커니즘이 비동기 작업을 직접 관리해주지는 않습니다.
애플리케이션의 정보 흐름을 매우 명확하게 만듭니다. 버그가 발생한 상황의 state를 store, 해당 action, 그리고 view에서 action을 트리거한 시점까지 역추적할 수 있습니다. 이 과정의 모든 단계가 동기적이므로 action에 대응하는 state를 예측할 수 있으며 시점(Timing)에 따른 예기치 못한 상황이 발생하지 않습니다.
제어의 역전(IoC)
store가 외부에서 업데이트되는 대신 스스로 내부 state를 업데이트하므로, 다른 부분에서는 구체적인 state 변화를 알 필요가 없습니다. 상태 변화는 오직 store와 관련됩니다. store는 action만 받기 때문에 store에 대한 유닛 테스트를 수행하려면 초기 상태를 설정하고 action을 보낸 뒤 최종 상태가 예상과 일치하는지만 확인하면 됩니다.
의미론적(Semantic) action
store는 action에 따라 state를 업데이트하므로, action은 일련의 state 업데이트 작업에 대한 이름과 같은 의미를 갖습니다. action은 상태를 어떻게 업데이트할지는 모르지만 예상 결과를 설명하며, 애플리케이션의 특정 기능을 설명하기 때문에 상대적으로 안정적입니다(action을 수정할 일이 거의 없습니다). 예를 들어 MARK_THREAD_READ는 특정 메시지를 읽음 상태로 바꾸고 싶다는 의도를 나타냅니다.
추가적인 의미 정보는 상태 변화를 추적하는 데 도움이 되며, Redux DevTools와 같은 디버깅 도구를 통해 상태 변화를 추적할 수 있게 해줍니다.
연쇄 action 없음
하나의 action이 다른 action을 트리거하는 것을 허용하지 않음으로써 연쇄 업데이트로 인한 디버깅 복잡성을 방지합니다. 따라서 action은 '원자적'이며 복잡한 계층 관계가 없습니다.
5. 규약
Best Practices 섹션에 언급된 Flux의 도덕적 관례입니다.
store
-
데이터를 캐싱함
-
데이터 접근을 위한
getter만 노출하고setter는 제공하지 않음 -
dispatcher로부터 온 특정action에 반응함 -
데이터가 변경될 때마다 change 이벤트를 트리거함
-
오직 dispatch 과정 중에만 change 이벤트를 트리거함
내부 상태를 유지하고 내부에서만 상태를 업데이트하며, 특정 action에 집중합니다. 데이터 변경 시 change를 트리거하고, dispatcher에 의한 것이 아니면 다른 상황에서는 트리거하지 않습니다.
action
- 사용자의 행위를 설명하며
setter가 아님 (예:set-page-id대신select-page여야 함)
container
-
view를 제어하기 위한 React 컴포넌트 -
기본 역할은
store로부터 정보를 수집하여 자신의state에 저장하는 것임 -
props와 UI 로직을 포함하지 않음
사실상 controller-view이며, 일반 view와의 차이점은 위와 같습니다.
view
-
container에 의해 제어되는 React 컴포넌트
-
UI와 렌더링 로직을 포함함
-
모든 정보와 콜백을
props로 받음
일반적인 view이며 특별한 점은 없습니다.
아직 댓글이 없습니다