1. Flux란1-1. 역사1-2. 요약해결하고자 한 문제점원인해결 방법결과효과1-3. 해결하고자 한 문제점사례: 채팅 버그설계 개선: Client MVC1-4. 해결 방법 Controller 사용 → Controller 제거, Event 기반으로View는 Model의 상태를 구독하고 자동 반영API 데이터 그대로 사용 → 클라이언트 상태 모델 추가렌더링 과정 단순화 (렌더링 원인 명확화)1-5. 도입 효과2. (WIP) Flux 도입Flux를 사용하기 적절한 경우Flux가 부적절한 경우3. (WIP) Redux와의 차이점 동일한 점유사점차이점부족한 점4. (WIP) Flux 예제 실습 - Todo List4-1. Todolist 기능 목록4-2. list, delete, toggle 기능 구현 - 실습 내용addTodo 구현delete, toggle 구현
- 이 글의 내용
- Flux가 무엇이고, Redux와의 차이점을 이해합니다.
- Flux를 실제로 사용해보고 구체적으로 각 구성 요소를 이해합니다.
1. Flux란
- Flux는 Facebook 개발자들이 React로 View를 구축할 때 애플리케이션 상태를 관리하기 위해 개발한 라이브러리이며, 당시 Facebook에서 실제로 사용된 라이브러리입니다.
- 특히 상태 간에 뷰를 자동으로 re-render하는 React의 선언적 프로그래밍 스타일과 잘 어울립니다.
- (개인적으로는 React를 단순한 View로 활용할 때 쓰기 좋게 MVC를 개량한 모델이라는 생각이 듭니다.)
1-1. 역사
- Flux는 F8 2014 컨퍼런스에서 페이스북 개발자들이 오픈소스로 공개하고 발표했습니다. (10:50 ~ 24:00)
- (tmi) React는 JSConf US 2013 컨퍼런스에서 페이스북 개발자들이 오픈소스로 공개하고 발표했습니다.
1-2. 요약
- ‣
해결하고자 한 문제점
- 채팅 회귀 버그들이 자주 발생
- 소스 코드의 다른 부분을 수정했을 때 채팅 회귀가 발생
원인
- 예측하기 어려운 렌더링 결과(코드 실행 결과)
- 좋지 못한 설계 - 과도하게 복잡한 Controller 코드
- Controller는 이벤트, API 응답을 처리
- 특정 이벤트, API 응답을 처리할 때, 갱신할 Model이 많아질수록, Controller가 복잡해짐
- Controller가 복잡하므로 실행 결과를 예측하기 어려워짐
- 연관 Model 간 연쇄 갱신 사례:
DM 목록 모델
에서 특정 DM을 읽음으로 갱신하는 경우읽지 않은 개수 모델
에서 개수를 갱신- (MVC 참고)
- ‣
해결 방법
- 렌더링 결과를 더 쉽게 예측할 수 있게 설계
- Action → Dispatcher → Store → View
- Controller의 책임을 Model과 View로 이동
- Model의 public setter 제거
- Model의 setter를 활용하던 Controller 제거
- Model이 Event를 수신하고 직접 처리
- View는 Model의 변경 사항을 수신해 직접 처리 (React)
- 렌더링 원인을 명확화
- 상태 변경은 Action을 발행해야만 가능하게 변경
- 렌더링 과정을 단순화
- 각 렌더링의 원인은 하나의 Action이도록
(prevState, action) ⇒ nextState
- State 마다 렌더링 수행
- 상태 반영(reduce) 도중에는 Action 발행을 금지 (Store → Action ❌)
- 별도의 State 변경이 필요한 경우, 새로운 Action → … → View 단계를 거치도록.
결과
- 단방향 데이터 흐름
- Action → Dispatcher → Store → View (→ Action)
- Event, Web API를 처리할 때 Controller가 하지 않고, Action 발행만 하는 모습.
효과
- 원인이었던 채팅 회귀 버그 해소
- 버그의 원인 파악이 더 쉬워짐
- 단위 테스트 작성이 더 쉬워짐
1-3. 해결하고자 한 문제점
- 계속 반복되는 채팅 버그의 회귀때문에 구조적인 문제가 있다고 판단하고, 구조를 개선하고자 했다고 합니다.
사례: 채팅 버그
- 당시 facebook에는 계속해서 채팅 회귀 버그들이 있었다고 합니다.
- 가장 유명한 것이 읽지 않은 메시지 개수 버그라고 합니다.
- 채팅 기능에는 Model이 여러 개가 있어 Controller에서 여러 개의 Model, View를 다루고 있었습니다.
설계 개선: Client MVC
- 처음에는 문제가 없었지만, Model, View가 많아지고 서로 연관될수록 기능을 추가하는 과정이 어려워졌고, 전체적으로 동작의 흐름을 예측할 수 없어졌다고 합니다.
1-4. 해결 방법
Controller 사용 → Controller 제거, Event 기반으로
- AS-IS: 이벤트, API 응답 처리 시 Controller(MVC)로 처리
- (ex)
new message
API Handler ChatTab
,MainMessages
,UnseenThreads
모델의 상태를 갱신- 화면을 리렌더링
- TO-BE: 각 UI 요소에서 응답해야 하는 Event를 직접 처리
- API 응답 수신 시 Action이 발행되고, 관련된 Model들에게 전달
- 각 Model이 독립적으로 Action을 처리
- (ex) 신규 메시지가 있는 경우
new message
Action을 발행ChatTab
,MainMessages
모델은 UI 상에 메시지 추가UnseenThreads
모델은 조회하지 않은 메시지 목록에 추가- (ex) 메시지를 읽은 경우
mark seen
Action을 발행UnseenThreads
모델은 조회하지 않은 메시지 목록에서 해당 메시지를 제거
View는 Model의 상태를 구독하고 자동 반영
- AS-IS: Controller가 직접 View의 갱신을 호출
- (ex) 신규 메시지가 있는 경우
messagesView.appendMessage(newMessage);
- TO-BE: View는 Model을 구독하고 변경 사항이 있으면 스스로 re-render 수행
- (ex) 신규 메시지가 있는 경우
ChatTab
,MainMessages
모델은 UI 상에 메시지 추가messagesView
는messages
를 받아 화면을 스스로 갱신
API 데이터 그대로 사용 → 클라이언트 상태 모델 추가
- AS-IS: 신규 메시지 알람 기능의 경우
new message
이벤트만 존재 - 이벤트마다 그 개수만 더하고 빼는 방식을 사용
- (도메인 계층이 별도로 없고 API 반환 값을 그대로 사용하는 코드만 있는 방식)
- TO-BE:
unseen messages(조회하지 않은 메시지)
라는 클라이언트 상태를 새로 정의 - “조회하지 않은 메시지 목록”을 명시적으로 관리
- 조회하지 않은 메시지의 개수는 이 목록의 길이를 활용
- (클라이언트에 도메인 계층이 생겨 코드의 의도가 더 명확해지는 방식)
렌더링 과정 단순화 (렌더링 원인 명확화)
- AS-IS: Controller에서 View에 반영되기 전에 여러 Model을 갱신
- 여러 단계가 한 번의 렌더링 사이클에서 실행되므로 복잡
- (ex)
new message
,mark unseen
이 한 번에 실행되고 화면에 반영
- TO-BE: 한 단계에 한 번의 렌더링으로 단순화
- 신규 상태를 View가 렌더링하기 전까지 상태 갱신을 금지
- Store에서 스스로 신규 Action을 발행할 수 없도록 제한
- 항상
(prevState, action) => nextState
인 구조 (action[]
❌) - (ex)
new message
발행 이후 렌더링, 이후mark unseen
발행 - Action → Dispatcher → Store → View 순서를 항상 지켜야 함
1-5. 도입 효과
- 채팅 버그 해소
- 신규 입사자의 코드 파악 시간 감소
- 버그 원인을 더 쉽게 찾을 수 있게 개선
2. (WIP) Flux 도입
Flux를 사용하기 적절한 경우
Flux가 부적절한 경우
3. (WIP) Redux와의 차이점
동일한 점
- Action이라는 Event 기반입니다.
- 단방향 데이터 흐름입니다.
유사점
reduce
함수가 있습니다.- Redux에서 reducer는 함수 형태로 구성합니다.
- Flux에서 reduce는 Store의 메소드입니다.
connect
함수와 같은create/createfunctional
함수가 있습니다.- 일종의 mapToStateToProps가 있습니다.
- Flux는 별도로 구독할 Store의 배열을 추가로 입력받습니다. (Redux는 단일 스토어이므로 필요 없습니다)
차이점
- 기능 마다의 Store를 사용하며, Multi Store로 앱이 구성됩니다.
- Dispatcher라는 구성 요소가 있습니다.
- Multi Store때문에 필요한 요소입니다.
- 모든 Store는 Dispatcher에 등록됩니다.
- Dispatcher는 Action 실행의 단일 진입점입니다.
- Dispatcher는 등록된 모든 Store에 Action을 전달합니다.
- 특정 Action 처리 시 Store 간의 순서를 명시적으로 결정해야 합니다.
- 이를
waitFor
라는 헬퍼 함수를 호출함으로써 구현합니다.
- Store 정의 시에는 Store를 상속받는 class로 정의해야 합니다. (React의 Component class 같이)
mapDispatchToProps
가 없습니다. 대신 Action Creator가 순수 함수가 아니라, dispatch 호출까지 수행합니다.
부족한 점
- Middleware 정도의 확장성은 없습니다.
- 대신 Store에서 비동기적으로 Action을 발행합니다.
- (Middleware는 대부분 비동기 동작을 하기 때문에 문제가 없어보입니다.)
4. (WIP) Flux 예제 실습 - Todo List
- 출처
- MVC 예제
- ‣
- Chat 예제 (3.1.0 이후에 제거됨)
- ‣
- 멀티 스토어의 waitFor 사용 예시를 보기 위해 확인 필요
4-1. Todolist 기능 목록
TodoList의 기능은 아래와 같습니다.
- list 조회
- 신규 item 생성 (텍스트 컨텐츠 등록)
- item 제거
- item의 완료 여부 toggle
- item의 텍스트 컨텐츠 수정
4-2. list, delete, toggle 기능 구현 - 실습 내용
addTodo 구현
- 단일 todoStore에 addTodo 액션을 처리할 수 있게 구현한 모습
- 마운트 후 addTodo를 단순하게 호출해서 화면만 구성
- flux가 Redux와 유사한 점
- Store 선언이 조금 다르지만,
reduce = (state, action) => state
인 메소드가 존재 - reduce 메소드로 action을 반영한 상태를 반환
- flux가 Redux와 다른 점
- Dispatcher의 존재
- Dispatcher의 인스턴스를 싱글톤으로 사용
- Store에 Dispatcher를 등록
- Action Creator가 있는데, Dispatcher를 직접 import해서 호출
- redux에서는 actionCreator는 단순히 action 객체를 반환
- 컴포넌트에서 mapDispatchToProps으로 dispatch를 실행
- Store를 선언하며, class로 구성
- Store의 인스턴스를 싱글톤으로 사용