Redux 소개 및 정리

Redux를 소개하기전에 알아야할게 하나 있다.

Flux 를 알고가야한다 물론 몰라도 상관은 없지만 “왜?” Redux가 나오게 된건지는 알아둬서 나쁠건 없다고 본다.

Flux는 애플리케이션에서 데이터를 취급하기 위한 패턴(pattern) 이며 MVC 모델의 단점을 보안하고자 나온 패턴이다

MVC패턴과 Flux의 구조

MVC

MVC 패턴

MVC는 양방향 데이터 흐름이기 때문에 작은 규모가에서는 괜찮을지 모르지만 규모가 커질 수 록 복잡해지기 때문에 예측하지 못하는 오류 등 문제점이 발생한다.

페이스북 같은 큰 규모에서는 당연히 문제가 생긴다 그 문제중 대표적인게 ‘알림 버그’였다. 알림버그는 메시지 알림이 와서 눌러봤지만 아무런 메시지가 없던 버그 였다. 페이스북 개발자들은 이 문제를 해결했지만 단기적인 조치일뿐 업데이트를 하다보면 또 나타나곤 했다.

페이스북은 알림 버르의 고질적 문제를 해결하고자 양방향 데이터 흐름이 아닌 단방향 데이터 흐름인 Flux를 선택한다.

Flux

MVC 패턴

Flux는 MVC와는 다르게 단방향 데이터 흐름이기때문에 변화를 예측하기가 쉬워진다.

Flux를 보면 Action, Dispatcher, Store, View으로 구성되어 있다 이제 하나하나 살펴보자

The Action Creator(액션 생성자)

모든 변경사항과 사용자와의 상호작용을 담당하고 있고 애플리케이션의 상태를 변경하거나 뷰를 업데이트하고 싶다면 액션을 생성해야만 한다.

액션 생성자의 경우는 나머지 시스템들이 이해할 수 있게 요청에 대해서 쉽게 포맷해서 바꿔준다.

액션 생성자는 전보기사와 같은 일을 한다 알바팻을 기계들이 처리할 수 있는 모스부호로 바꾸는 것처럼 그에 맞는 형태로 포맷해준다.

액션 생성자는 받은 액션(상태변화나,업데이트)을 바꾼뒤 디스패쳐에게 넘겨준다.

Dispatcher(디스패쳐)

디스패쳐는 액션을 보낼 필요가 있는 모든 스토어와 연결해 줄 수 있다. 액션을 보낼 필요가 있는 스토어에게 액션을 보낸다.

전화 교환대에서 등록된 모든 전화에 연결이 가능하듯 디스패쳐는 전화 교환대 같은 역활을 해준다.

디스패쳐의 처리는 동기적(synchronously)으로 실행되어서 다수의 요청을 처리해야하는 경우 도움을 준다. 만약 업데이트의 우선순위를 결정해야한다면 waitFor() 을 사용해서 처리할 수 있다.

Flux의 디스패처는 액션 타입과는 관계없이 등록된 모든 스토어에 요청을 보낸뒤에 스토어에서 처리할지 말지를 결정하도록 한다.

Store(스토어)

애플리케이션 내의 모든 상태와 그와 관련된 로직을 가지고 있다.

스토어는 정부의 관료 같은 역활을 맡으며 모든 상태 변경은 반드시 스토어에 의해서 결정되어야한다. 상태 변경을 직접 스토어에게 요청할 순 없다.

스토어에는 개별적으로 설정자(setter)가 존재하지 않으므로, 상태 변경을 요청하기 위해서는 반드시 모든 정해진 절차를 따라야만 한다. 쉽게 말하면 무조건 정해진 절차로만 거쳐서 액션을 보내야한다. (액션 -> 디스패쳐 -> 스토어)

View(뷰)

스토어에서 뷰에게 전달된 요청은 컨트롤러 뷰 가 자신의 아래에 있는 모든 뷰에게 새로운 상태를 넘겨준다.

뷰는 발표자와 같으며 컨트롤러 뷰는 발표자에게 지시를 내려주는 형태이다 뷰는 받은 요청을 처리하여 사람들이 이해할 수 있는 포맷(HTML)으로 바꿔준다.

어떻게 함께 동작할까?

위에 소개한 구성들이 이제 어떻게 동작하는지 살펴 볼 필요가 있다.

준비(The Setup)

래플리케이션이 초기화를 하면 딱 한번 준비과정을 거친다.

준비과정

  1. 스토어에 디스패쳐에 액션이 들어오면 알려달라고 말한다
  2. 컨트롤러 뷰는 스토어에게 최신 상태를 묻는다
  3. 스토어가 컨트롤러 뷰에게 상태를 주면 렌더링하기 위해 모든 자식 뷰에게 상태를 넘겨준다
  4. 컨트롤러 뷰는 스토어에게 상태가 바뀔 때 알려달라고 다시 부탁한다

데이터 흐름(The Data Flow)

이제 준비과정이 끝났으니 애플리케이션은 유저 입력을 위한 준비가 완료된다.

사용자의 입력으로 인한 액션이 생기는 과정을 보자.

데이터 흐름

  1. 뷰는 액션(액션 생산자)자에게 액션을 준비하라고 말한다.
  2. 액션 생성자는 액션을 포맷에 맞게 만들어 디스패쳐에게 전달
  3. 디스패쳐는 액션이 들어온 순서에 따라 스토어로 전달하며, 각 스토어는 모든 액션을 받게 되지만 필요한 액션만 골라서 상태를 필요에 맞게 변경
  4. 상태 변경이 완료되면 스토어는 자신을 구독(subscribe)1하고 있는 컨트롤러 뷰에게 그 사실을 전달
  5. 상태가 변경된 사실을 연락 받은 컨트롤러 뷰는 스토어에게 변경된 상태를 요청
  6. 스토어가 새로운 상태를 넘겨주면, 컨트롤러 뷰는 자신의 아래의 모든 뷰에게 새로운 상태에 맞게 랜더링 하라고 알린다.

Redux

이제 Flux가 어떤 것인지 개념과 구조를 다 확인 했으니 Redux로 넘어갈 차례다.

Flux의 문제점이 뭐길래 그걸 보완해서 나온것이 Redux인가? 부터 생각이 든다.

무슨 차이점이 있을까?

Flux와 Redux의 차이점

Flux의 문제점이 무엇이고 어떻게 해결해서 Redux를 만들었는지 알아보자

Flux의 문제점

  1. 스토어의 코드는 애플리케이션 상태를 삭제하지 않고는 리로딩(reloading)이 불가능하다.

    Flux의 스토어는 두 가지를 포함하고 있는데

    1. 상태 변환을 위한 로직
    2. 현재 애플리케이션 상태

    스토어 객체 하나가 이 두가지를 가지고 있는 것은 핫 리로딩2을 할 때 문제점이 생긴다. 새로운 상태 변환 로직을 위해 스토어 객체를 리로딩 하면 스토어에 저장되어 있는 기존의 상태까지 읽어버리는 데다가 스토어와 시스템의 나머지 부분 사이에 있는 이벤트 구독(event subscription)까지 망가진다.

    해결방법

    두 기능을 분리하고 한 객체는 애플리케이션 상태만을 가지게하고 해당 객체는 리로딩하지 않도록한다. 다른 한 객체는 모든 상태 변환 로직을 가지도록 하여 상태를 가지고 있지 않으므로 걱정없이 리로딩 할 수 있게 만든다.

  2. 애플리케이션 상태는 매 액션마다 재기록 된다.

    시간 여행 디버깅을 하기 위해서는 상태 객체의 모든 버전을 기록해 놔야지 쉽게 이전 상태로 돌아갈 수 있다.

    매번 상태가 새로 바뀔 때마다 이전 애플리케이션의 상태를 배열에 추가해야하는데 JavaScript의 동작 방식에서는 단순히 상태를 가진 변수를 배열에 추가하는 것만으로는 부족하다.

    이 방식으로는 애플리케이션 상태의 스냅샷(snapshot)2을 생성하는게 아니라 같은 객체를 가리키는 새로운 포인터(pointer)3를 만들 뿐이다.

    제대로 동작하게 만들기 위해서는, 각각의 버전이 완벽히 독립된 객체가 될 필요하며, 그러면 이전 상태들이 실수로 수정되는 일은 일어나지 않을 것이다.

    해결방법

    액션이 스토어에 전달되었을때 기존의 상태를 복사한 뒤 복사본을 수정한다. 그 전에는 기존의 상태를 수정했지만 복사를 하면 독립된 객체가 된다.

  1. 서드파티 플러그인(third-party plugin)이 들어갈 좋은 장소가 없다.

    개발할때 개발자가 자신의 코드를 쉽게 수정하고 테스트 해볼 수 있도록 개발자 도구를 쉽게 쓸 수 있도록 만들어야한다.

    개발자 도구를 사용할 수 있게 하기위해서는 확장점(extension point)4이 필요하다.

    하나의 예로는 로깅(Logging)5이 있는데 매 액션마다 console.log() 를 실행한다고 가정하면, 액션이 들어왔을 때 그 액션의 결과로서 만들어지는 새로운 상태를 로긴할 것이다.

    하지만 디스패쳐의 업데이트, 각 스토어의 업데이트를 구독해야만 하지만 이것은 서드파티 모듈이 쉽게 할 수 없다.

    해결방법

    시스템의 부분을 다른 객체들로 쉽게 감쌀 수 있게 만들면, 객체들은 약간의 추가기능들을 시스템의 부분에 추가할 수 있다.6

    상태 변환 로직(state change logic)을 구조화하기 위해서 트리를 사용한다. 트리에서는 상태가 변했다는 것을 뷰에게 알리기 위해 스토어는 단 하나의 이벤트만 보내면 된다. 이 이벤트는 모든 상태 트리가 처리 된 뒤에 보낸다.

역활

액션 생성자(action creators)

Redux는 Flux와 역활을 하며 Flux와는 다르게 Redux 액션 생성자는 디스패쳐로 액션을 보내지 않고 포맷을 바꾼 뒤 액션을 돌려준다. (뷰 -> 액션생성자 -> 뷰)

스토어(store)

스토어도 Flux의 정부관료와 같다. 하지만 Flux와 다른점은 Flux에서는 다수의 스토어를 가질 수 있었던 반면에 Redux의 스토어는 단 하나의 스토어만 가지고 있기 때문이다. 만약 이전과 같이 혼자서 모든 걸 처리하려 한다면 양이 너무 많아 짐으로 일을으 분산 시켜 다른곳에서 처리하도록 한다.

Redux의 스토어는 상태 트리(state tree) 전체를 유지하는 책임을 맡는다. 액션이 들어왔을때 어떤 상태 변화가 필요한지에 대해서는 리듀서(reducer)가 그 일을 맡는다.

리듀서(the reducers)

스토어는 액션이 어떤 상태 변화를 만드는지 알아야 할때 리듀서에게 묻는다. 루트 리듀서는 애플리케이션 상태 객체의 키를 기준삼아 상태를 나누고 조각을 처리할 리듀서에게 넘겨준다.

리듀서는 서류를 복사하는 일을 하는 직원 같으며, 넘겨받은 상태를 예전상태로 변경하지 않는다. 대신 새로운 복사본을 만든 후 거기에 모든 변경사항을 적용한다.

뷰 [스마트&우둔한] (the views: smart and dumb)

Redux는 Flux와 같이 두 가지(smart&dumb)의 뷰가 존재한다. 스마트 뷰는 관리자처럼 행동하며 Flux의 컨트롤러 뷰 보다 몇가지 규칙을 더 따른다.

  • 스마트 컴퍼넌트는 액션 처리를 책임지며 우둔한 컴퍼넌트에게 액션을 보내야 할때 props를 통해서 함수를 보낸다. 우둔한 컴퍼넌트는 함수를 콜백으로써 단순히 호출만 한다.
  • 스마트 컴퍼넌트는 자기 자신의 CSS style을 가지고 있지 않는다.
  • 스마트 컴퍼넌트는 DOM을 거의 가지고 있지 않으며 DOM 요소들을 관리하는 우둔한 컴퍼넌트를 관리한다.

우둔한 컴포넌트는 액션에 직접 의존성을 가지지 않으며 모든 액션을 props를 통해서 넘겨 받기 때문이다. 위 방법은 우둔한 컴포넌트는 다른 로직을 가진 다른 어플리케이션에서 재사용될 수 있다는 뜻이다. CSS style도 포함하고 있으며 style props를 받아서 병합해서 변경가능하다.

뷰 레이어 바인딩(the view layer binding)

Redux는 스토어를 뷰에 연결하는데 약간의 도움이 필요하다. 스토어와 뷰를 묶어줄 역활을 하는게 뷰 레이어 바인딩이다. React에서 뷰 레이어 바인딩 역활을 해주는건 react-redux7가 그것이다.

뷰 레이어 바인딩은 모든 컴포넌트를 스토어에 연결하고, 많은 기술적인 세부사항을 처리해서 트리(뷰 트리: view tree) 구조가 세부사항을 신경 쓰지 않도록 처리해준다.

뷰 레이어 바인딩은 3가지 모습이 있다.

  1. 공급 컴포넌트(provider component): 컴포넌트 트리를 감싸는 컴포넌트로써 connect() 를 이용해 루트 컴포넌트 밑의 컴포넌트들이 스토어에 연결되기 쉽게 만들어준다.
  2. connect() 는 redux-react기 제공하는 함수이며, connect()( 를 이용해서 컴포넌트를 감싸주면 애플리케이션 상태를 업데이트를 받을 수 있다. 그러면 connect()selector() 를 이용해서 모든 연결을 만들어준다.
  3. selector() 는 직접 만들어야 하는 함수이며, 애플리케이션 상태 안의 어느 부분이 컴포넌트에 props로써 필요한 것인지 지정한다.

루트 컴포넌트(the root component)

루트 컴포넌트는 모든 React 애플리케이션에서 존재하며 단지 컴포넌트 계층 구조에서 가장 위에 위치한 컴포넌트인데 Redux에서는 추가로 기능이 있다. 모든 팀이 일을 하도록 하는 임무를 가진다. 스토어를 생성하고 무슨 리듀서를 사용할지 알려주며 뷰 레이어 바인딩과 뷰를 불러온다.

루트 컴포넌트는 애플리케이션을 초기화한 뒤로는 거의 하는 일이 없다. 화면을 갱신도 더는 신경 쓰지 않는다. 화면 갱신은 뷰 레이어 바인딩의 도움으로 아래의 컴포넌트들이 맡아서 처리한다.

어떻게 작동하는가

준비

  1. 스토어를 준비한다
  2. 스토어와 컴포넌트 사이의 커뮤니케이션을 준비한다.
  3. 액션 콜백을 준비한다.

데이터 흐름

redux-data

  1. 뷰가 액션을 요청하고 액션 생성자가 포맷을 변환한 뒤 돌려준다.
  2. bindActionCreators()가 준비과정에서 사용되었으면 자동으로 액션이 보내지지만 그게 아니라면 뷰가 직접 보낸다.
  3. 스토어가 액션을 받고 현재 애플리케이션 상태 트리와 액션을 루트 리듀서에게 보낸다.
  4. 루트 리듀서는 상태 트리를 조각으로 나눈 뒤 알맞은 서브 리듀서로 상태 조각들을 넘겨준다.
  5. 서브 리듀서는 받은 상태 조각을 복사한 뒤, 그 복사본을 변경한다. 루트 리듀서에게 변경된 복사본을 돌려준다.
  6. 모든 서브 리듀서가 변경 된 상태 조각들을 돌려주면, 루트 리듀서는 이 상태 조각들을 한데 모아 상태 트리로 만든 뒤 스토어로 돌려준다. 스토어는 새로운 상태 트리를 옛날 상태 트리와 바꾼다.
  7. 스토어는 뷰 레이어 바인딩에게 애플리케이션 상태가 변경되었다는 것을 알린다.
  8. 뷰 레이어 바인딩은 스토어에게 새로운 상태를 보내달라고 요청한다.
  9. 뷰 레이어 바인딩은 뷰에게 화면을 업데이트하도록 요청한다.

마치며

리엑트를 거의 혼자하다보니 깊게 공부할 일이 쉽지 않았는데 여러가지 찾아보고 Redux를 찾아보다보니 Flux를 알아야하고 Flux 처럼 패턴에 대해서도 가볍게 알아가는 시간이라 좋은 시간이였던거 같다. 실전에서 써보려면 한참 부족하지만 그래도 기본 지식을 정리를 한번 한걸로 큰 도움이 될꺼라 믿는다.

참고 자료


  1. 구독 또한 스토어의 내장함수 중 하나이다. Subscribe 함수는, 함수 형태의 값을 파라미터로 받아 옵니다. Subscribe 함수에 특정 함수를 전달해주면, 액션이 디스패쳐 되었을 때 마다 전달해준 함수가 호출 된다.

  2. 과거의 한 때 존재하고 유지시킨 컴퓨터 파일과 디렉터리의 모임 (출처-위키백과)

  3. 프로그래밍 언어에서 다른 변수, 혹은 그 변수의 메모리 공간주소를 가리키는 변수를 말한다. 포인터가 가리키는 값을 가져오는 것을 역참조라고 한다. (출처-위키백과)

  4. 기존의 코드에 서드파티 플러그인을 추가할 수 있는 장소

  5. 로그를 기록하는 행위

  6. 이런 확장점을 “enhancer” 또는 “higher order” 객체 혹은 미들웨어(middleware)라고 부른다.

  7. 이 부분을 잘 이해가 안되니 나중에 자세히 알아보자. 아직 redux를 잘 사용안해봐서 그런거 같다.