Redux 인터넷 강의!

Web 2016. 11. 9. 18:21

https://egghead.io/lessons/javascript-redux-store-methods-getstate-dispatch-and-subscribe

블로그 이미지

kuku_dass

,

출처 : http://huns.me/development/1953


리덕스(Redux) 애플리케이션 설계에 대한 생각

이 글은 리덕스를 이용하여 애플리케이션을 개발할 때, 설계를 고민하며 했던 생각을 정리한 글입니다. 리덕스 기초를 소개하는 글이 아니니 읽기 전에 참고하세요.

 

많은 사람들이 커피숍 테이블에 앉아 왁자지껄 시끄럽게 이야기를 주고 받는 모습의 사진
타인과 생각을 공유하고 의견을 주고 받으며 부딪히는 과정은 피곤하지만, 그 속에서 더 좋은 해법을 발견한다.

최근 진행하고 있는 모바일 웹 프로젝트는 리액트(React)와 리덕스(Redux) 조합을 이용하고 있다. 모든 프레임워크가 그렇듯이, 리덕스 역시 모든 문제를 해결하지 않는다. 남은 부분은 개발자의 몫이다. 리덕스 공식 문서의 FAQ는 개발하면서 한 번쯤은 고민해봤을 법한 문제를 리덕스로 해결하는 방법을 친절하게 안내하고 있어 참고할만하지만, 확실하게 딱 잘라서 결정해주는 맛은 없다.

사실 가치 판단의 문제인 경우가 대부분이라, 딱 잘라서 정답을 제시한다는 것 자체가 어렵다. 선택지라도 알려주는 게 어딘가. 무엇이 더 좋은 선택인지는 전체 상황을 고려해서 결정해야 하며 프로젝트 초기에 어느 정도 설계의 방향성을 팀원 간에 합의를 하는 것이 좋다. 그렇지 않으면 일관성을 잃어버린 코드가 제멋대로 자라다가 급기야는 시스템을 좀먹는다.

동료들과 프로젝트를 본격적으로 진행하기 전에 설계 방향을 놓고 많은 이야기를 했다. 고민이 등장할 때마다 메모를 해두었는데, 그렇게 마구 흩어놓은 리덕스 디자인에 대한 생각을 정리하는 차원에서 주저리주저리 떠들어 볼 생각이다. 앞으로 써 내려갈 내용 중에는 모두가 동의한 부분도 있고, 그렇지 않은 부분도 있다. 나는 그것이 옳다고 생각하지만 누군가는 더 나은 다른 방법이 있다고 믿는다. 세상사가 다 그런 거 아닌가. 논의하고 부딪히는 과정은 피곤하지만, 그 속에서 더 좋은 해법을 발견한다.

리덕스를 이야기하면서 플럭스 유틸즈(Flux-utils)와 비교를 안 할 수가 없다.

“뭐가 다른 걸까?”

제일 먼저 리듀서(reducer)가 눈에 들어왔다. 이름부터 난해하다. 리듀서라니. 리덕스를 이해하려면 리듀서를 이해해야 할 것 같다. 그래서 설계의 시작도 리듀서다.

리듀서 들여다보기

플럭스 유틸즈와 비교했을 때,  리덕스의 가장 눈에 띄는 특징은 ‘단일 스토어, 다수의 리듀서’ 정책이다. 시스템의 도메인 레이어라 할 수 있는 스토어에서 모든(대부분?) 상태를 관리하는 플럭스 아키텍처 위에서는, 커지는 코드 베이스에 맞춰 스토어 역시 쉽게 비대해진다. 이런 상황이 오면 하나였던 스토어를 다수의 스토어로 분리한다.

이를 위해 플럭스 유틸즈는 스토어를 추상화한 객체를 제공한다. 그게 설계자의 의도인지는 모르겠지만 구글링을 하면 이런 사례들이 많이 눈에 띈다. 액션이 다수의 스토어에 비동기 요청을 던졌을 때, 각 요청을 처리하는 시점을 제어하는 API(waitFor 같은)를 통해서도 설계자의 의도를 추측할 수 있다.

이에 반해 리덕스는 단일 스토어를 유지하되, 상태 처리에 대한 책임을 스토어의 하위에 있는 리듀서에게 넘기는 ‘단일 스토어, 다수의 리듀서’ 전략을 택했다. 리덕스에서 스토어는 컴포넌트와 리듀서를 연결하는 아주 얇은 레이어일 뿐이다. 상태와 상태를 처리하는 행위는 스토어가 아닌 리듀서가 책임진다. 스토어는 여러 개의 리듀서를 가질 수 있다. 각각의 리듀서가 처리하는 상태는 최종적으로 최상위 리듀서가 시스템의 상태 트리로 만들어서 스토어를 구독하는 구독자에게 전달한다.

물론 플럭스 유틸즈를 이용해도 하나의 스토어를 두고 상태 처리 책임을 분리하는 방식으로 설계할 수 있고, 리덕스 역시 여러 스토어를 구현할 수 있다. 둘 다 어떤 한 가지 정책을 강제하지는 않는다. 다만 기저에 깔린 설계 철학이 어떤 쪽에 더 가까운지를 생각해봤을 때 그렇다는 뜻이고, 이 철학에 맞춰 구현할 때 가장 자연스럽다.

이 지점에서 분리해야 하는 대상의 본질이 스토어와 액션을 결합하는 인터페이스가 아니라 스토어가 관리하는 상태 데이터와 그 데이터를 처리하는 행위에 있다는 사실을 알 수 있다. 액션과 스토어를 결합하는 방식은 스토어가 어떤 상태를, 어떻게 책임지는지와는 상관없이 모두 동일하다. 이 공통 인터페이스를 플럭스 유틸즈는 상속으로 해결한다. 아래의 링크를 따라가보면 플럭스 유틸즈가 제공하는 스토어 객체를 확인할 수 있다. 플럭스 유틸즈는 모두 세 종류의 스토어 객체를 제공한다.

http://facebook.github.io/flux/docs/flux-utils.html#content

아래의 코드는 플럭스 유틸즈의 ReduceStore를 상속하여 스토어를 정의하는 예제 코드다. 이 코드는 Flow라는 정적 타입 검증기를 사용하고 있어 코드가 조금 낯설어 보일 수 있지만 타입 선언만 제거하면 그냥 자바스크립트다.

리덕스는 스토어를 좀 더 상위 수준으로 추상화하고 대신 하부 로직을 분리해서 책임을 분산시킨다. 아래의 코드를 보고 알 수 있듯이 리덕스에서 스토어는 내부 실체가 보이지 않는다. 그저 리듀서만 보일 뿐이다. 상태 처리 로직만 작성하면 나머지는 리덕스가 알아서 결합한다.

리듀서와 순수 함수에 대한 생각

리덕스의 공식 문서에 있는 예제들은 순수 함수를 적극 활용한다. Dan Abramov가 함수형 프로그래밍에 영감을 받아서 리덕스를 만들었기 때문인 듯하다. 순수 함수는 아래와 같은 특징을 갖는 함수를 말한다.

  1. 함수 밖에 있는 데이터나 변수를 변경해서 의도치 않은 결과를 발생시키지 않아야 한다.
  2. 동일한 입력 데이터의 집합을 제공받으면 항상 동일한 연산 결과를 반환해야 한다.

특히 리듀서에 이런 철학이 강하게 묻어있다. 리듀서를 순수하게 유지하면 뭐가 좋은 걸까? 결론적으로 시스템이 복잡해져도 리듀서를 가능한 단순한게 유지할 수 있다.

입력을 받으면 입력 값을 처리하여 새로운 상태를 결과로 반환하는 게 전부다. 입력 값을 전달하는 것 외에는 함수 실행 중에 외부의 어떤 조건에도 영향을 받지 않는다. 하는 일이 단순하니 예측하기 쉽고, 테스트하기도 쉽다. 리덕스가 자랑하는 시간 여행 디버깅이 가능한 것도 리듀서의 이런 특성 덕이다.

하지만 리듀서를 함수로 구성하다 보니 행위와 데이터를 하나로 결합한 도메인 모델을 구성하기가 어렵고, 이로 인해 설계에 익숙하지 않은 어색한 지점이 발생(데이터의 정체성을 어떻게 정의할지 모호하다든지, 데이터를 가져오는 AJAX 요청을 도메인 레이어의 앞단에서 처리해야한다든지)하는 단점이 있다.

리듀서가 순수해야 한다는 뜻은 리듀서에 전달하는 값이 동일하다면, 리듀서가 돌려주는 값도 항상 동일해야 함을 의미한다. 그래서 xhr을 이용한 비동기 통신처럼 사이드 이펙트를 만들 수 있는 행위는 리듀서에 담지 않는다. 이는 리듀서가 연산을 수행하는 데 있어 리듀서 외부의 상태에 영향을 받지 않아야 한다는 뜻일 뿐, 그 안에 어떠한 로직도 없어야 한다는 뜻은 아니다. 비즈니스 로직은 처리 대상인 데이터와 가까운 곳에 위치하는 것이 좋기에, 리덕스 애플리케이션의 비즈니스 로직은 리듀서에 위치하는 것이 적절하다. 처리하려는 로직의 성격에 따라 리듀서 내부의 로직도 얼마든지 복잡해질 수 있고, 그러다 보면 리듀서 내에서 다른 여러 함수를 호출하는 경우도 자주 있다.

이 지점에서 오해하지 말아야 한 가지는, 리듀서를 ‘순수’하게 만들기 위해서 반드시 함수를 고집해야 하는 건 아니라는 점이다. 객체를 이용해서도 얼마든지 리듀서를 순수하게 만들 수 있다. 모든 건 구현하기 나름이니 리덕스에 함수형 철학이 묻어있다고 ‘함수형이 짱짱맨’이라는 생각은 하지 말자. 다만 리덕스를 이용하면 함수 단위로 코드를 작성하는 게 조금 더 자연스러울 뿐이다.

상태 트리 설계에 대한 고민

리덕스는 리듀서를 트리 구조로 분리함으로써 상태 관리에 대한 책임을 분산시킨다. 분리한 상태는 combineReduce 함수로 조합하여 최상위 리듀서가 시스템의 단일 상태 트리로 조합한다. 이 지점에서 시스템의 상태 구조가 드러난다.

리듀서를 너무 깊은 트리 구조로 만들면 리듀서 결합이 복잡해지고, 상태의 흐름이 잘 안 읽힌다. 그래서 최대한 평탄한(flat) 구조로 만들어야 한다. 하지만 너무 평탄해버리면 상태 간의 관련성을 가독성 있게 표현하기 어렵다. 어느 지점에서 균형을 잡아야 한다. 상태 트리 구성과 분류 기준을 선택하는 일은 리덕스 시스템의 큰 틀과 방향을 결정하는 중요한 의사결정이다.

‘너무 깊은 트리 구조’를 만들지 말라는 걸 중첩 JSON 구조로 데이터를 정의하지 말라는 걸로 오해하면 곤란하다. 단지 리듀서를 지나치게 중첩하지 말라는 소리다. 리듀서가 난립하면 결합한 상태 트리의 모습을 예측하기 어려워진다.

그렇다면 상태 트리는 어떠한 기준으로 설계를 하는 것이 좋을까? 처음에는 UI를 기준으로 상태 트리를 구성하는 걸 생각해봤다. 헤더가 있고, 푸터가 있고. 메뉴별로 상태 트리를 만들면 어떨까? 하지만 곧 생각을 접었다.

UI 단위로 상태 트리를 구성하면 UI와 리듀서가 강하게 묶인다. 어떤 상태를 상태 트리에 추가하면, 그 상태를 관리하는 리듀서가 있어야 한다. 반대 역시 마찬가지다. 결국 UI와 리듀서를 함께 끌고 다녀야 한다. UI는 변경이 잦은 부분이라 불안정한 UI와 리듀서를 엮어 버리면 리듀서 역시 불안정해진다.

결국 팀원들과 긴 논의를 거친 끝에, 기능을 기준으로 상태 트리를 설계하기로 했다. 기능 단위로 리듀서를 설계하면 UI와 리듀서의 관계를 약하게 만들 수 있고, UI의 변경이 미치는 파급효과를 줄일 수 있다. 상태 트리를 보고 시스템이 제공하는 기능을 유추할 수 있다는 점은 기능 단위로 상태 트리를 설계했을 때 얻을 수 있는 또 하나의 장점이다. 아래의 상태 트리 예제를 보면 도구막대와 컴포넌트 목록 관련 기능을 제공한다는 걸 유추할 수 있다.

상태 트리를 기능 단위로 정리했다면, 액션은 사용자의 행위를 기준으로 정리한다. 사용자가 수행하는 하나의 동작은 다수의 기능과 결합할 수 있다. 따라서 너무 당연한 이야기지만 액션과 리듀서의 관계는 1:N이다.

리덕스를 지탱하는 두 가지 리액트 컴포넌트

리덕스를 접할 때 애플리케이션의 ‘모든 상태’를 리듀서에서 관리하고, 컴포넌트 자체는 무상태로 만들어야 한다고 생각하는 경우를 가끔 본다. 결론부터 이야기하자면 이는 오해다. 리덕스는 모든 상태를 리듀서, 즉 스토어에서 관리할 것을 강제하지 않는다. 컴포넌트는 지역 상태를, 리듀서는 전역 상태를 관리할 수 있고 그래야  효율적이고 유연한 애플리케이션을 만들 수 있다.

그렇다면 무엇이 지역 상태고, 무엇이 전역 상태일까? 이를 이해하려면 우선 표현 컴포넌트와 컨테이너 컴포넌트를 이해해야 한다. 리덕스 공식 문서는 리액트 컴포넌트를 두 종류로 구분한다.

  1. 표현(Presentational) 컴포넌트
  2. 컨테이너(Container) 컴포넌트

이는 전혀 새로운 개념은 아니며 용어는 조금 다르지만 플럭스 유틸즈도 이런 방식으로 컴포넌트를 구분하고 있다. 이 둘에 대한 자세한 설명은 리덕스의 창시자인 Dan Abramov가 쓴 Presentational and Container Components에 잘 나와있다.  예전 리덕스 공식 문서에서는 smart component, dumb component라는 이름으로 이 둘의 기준을 모호하게 설명하고 있었으나, 최신 문서에서는 presentational, container라는 명칭을 사용하고 설명을 보완하여 이 둘의 경계를 분명하게 구분하고 있다.

표현 컴포넌는 리덕스 시스템과 별개인 컴포넌트로 화면에 컴포넌트를 어떻게 렌더링 할지를 결정하고, 사용자의 요청을 이벤트로 받아 상위 컴포넌트로 전달하는 역할만을 수행한다. 아래의 코드는 표현 컴포넌트의 예다.

이와 달리 컨테이너 컴포넌트는 표현 컴포넌트와 리덕스 시스템 사이의 연결 고리로 하위 컴포넌트에서 올라오는 요청을 해석하여 시스템의 어느 쪽으로 요청을 전달(mapDispatchToProps) 할 것인지를 결정하고, 전체 시스템의 상태 트리를 스토어로부터 넘겨받아 하위 컴포넌트로 전달(mapStateToProps) 하는 책임을 수행한다.

결국 컨테이너라는 존재에 의해 컨테이너가 아닌 모든 컴포넌트는 리덕스에 얽매이지 않는, 독립적인 개체로서 자율성을 가질 수 있는 셈이다. 필요한 지점에서 컨테이너를 이용해 컴포넌트를 리덕스 시스템과 결합하면 된다.

리덕스 공식 문서의 예제에 있는 표현 컴포넌트는 모두 무상태 컴포넌트(Stateless Component)다. 하지만 이는 예제가 너무 단순하여 내부에서 관리할 상태가 없기 때문이지, 모든 표현 컴포넌트가 무상태 컴포넌트여야 하는 것은 아니다.  컴포넌트를 드래그 앤 드롭할 때 현재 좌표 값이나 현재 스크롤 좌표 추적같이 매우 빈번한 연산을 요구하는 인터랙션 처리에 필요한 상태는 리듀서에서 관리할 경우 엄청난 비효율을 초래한다. 최대한 연산 수행 시간을 줄여야 하는 상황에서 상태를 변경할 때마다 아래와 같은 과정을 거쳐야 한다는 건 생각만 해도 아찔한 일이다. 결국 컴포넌트와 리듀서, 모두에게 각자의 상태를 들고 있어야 할 나름의 사정이 있다.

“action -> dispatch -> store -> reducer -> store -> compoent”

그렇다면 어떤 상태는 컴포넌트에 위치하고 어떤 상태는 리듀서에 위치해야 하는 걸까? 결론적으로 전역 상태는 리듀서가, 지역 상태는 개별 컴포넌트가 관리한다. 말은 쉽다. 하나씩 풀어보자.

전역 상태와 지역 상태, 무엇이 다를까?

전역 상태는 다음과 같이 정의할 수 있다.

“영속성을 가져야 하는 도메인 데이터, 또는 서로 다른 컨테이너나 컴포넌트 간에 공유해야 하는 UI의 상태”

에디터를 예로 들자면, 사용자가 작성한 문서에 대한 정보는 도메인 데이터로 영속성을 가져야 한다. 애플리케이션의 생애 주기 동안 관리하고, 시스템을 종료했다가 다음에 다시 데이터를 불러와서 동일한 상태를 재현할 수 있어야 한다. 이러한 상태는 신뢰할 수 있는 단일한 장소에서, 일관성을 가지고 관리해야 한다. 리덕스에서는 리듀서가 바로 그 지점이다.

그리고 도메인이 아닌 개별 UI의 상태지만 리듀서에서 관리를 해야 하는 경우가 있다. 서로 다른 컨테이너 간에 공유해야 하는 상태가 그렇다. 어떤 컴포넌트에 포커스가 들어오면 직전에 포커스를 가지고 있던 컴포넌트에서 포커스를 제거해야 하는 경우를 생각해보자. 포커스는 도메인 데이터가 아닌, UI의 상태일 뿐이다. 하지만 단 하나의 컴포넌트만 포커스를 가질 수 있다는 규칙상, 각 컴포넌트가 서로의 상태를 공유해야 한다. 이럴 때 포커스를 가지고 있는 컴포넌트에 대한 정보를 리듀서에서 상태로 관리할 수 있다.

자, 이제 지역 상태란 무엇일까? 지역 상태란 이렇게 정의할 수 있다.

“컴포넌트가 시스템과 상관없이 독립성을 갖고 표현 로직을 처리하는 데 필요한, 컴포넌트 내부에 캡슐화할 수 있는 상태”

컴포넌트의 모든 상태를 리듀서에서 관리하면 컴포넌트와 리듀서가 강하게 결합해 종속성이 생긴다. 컴포넌트가 어떤 인터랙션을 처리하려면 리듀서를 통해야만 하는데, 이는 성능 문제와도 연관이 있다. 앞에서 이야기했듯이 컴포넌트를 드래그 앤 드롭할 때, 현재 좌표 값 처리와 같이 매우 빈번한 연산을 수행하는 데 필요한 상태를 리듀서에서 관리하면 상당한 비효율이 발생한다. 따라서 이런 상태는 컴포넌트 내부에 캡슐화하여 컴포넌트가 자율적으로 상태를 처리할 수 있게 두는 게 좋다.

실제 제품을 개발할 때는 이것을 전역 상태로 둘지, 지역 상태로 둘지 결정하기 모호한 경우를 꽤 자주 만난다. 특히나 요구 사항이 완전치 않은 개발 초기에 이런 상황을 자주 접한다. 이럴 때는 우선 지역 상태로 분류하는 게 좋다. 전역 상태를 처리하는 과정이 지역 상태를 처리하는 과정 보다 번거롭고, 지역 상태가 전역 상태보다 외부와의 접점이 적기 때문에 나중에 상태의 성격을 변경할 때 수정 비용이 더 적게 들어간다. 그리고 지역 상태를 중심으로 자율성을 갖는 컴포넌트가 더 유연하다. 물론 유연하다는 것은 구현에 그만큼의 비용이 더 들어간다는 뜻이기도 하다. 따라서 상황에 따라 적절히 판단해야 하며 이는 개발자의 몫이다.

리액트와 리덕스의 결합이란…

컨테이너 컴포넌트는 리액트 컴포넌트에서 발생한 이벤트를 해석하여 리듀서로 전달하고, 스토어가 전파하는 상태 변경 이벤트를 받아서 변경한 상태 값을 컴포넌트에 전달하는 역할을 수행한다. 한 마디로, 리덕스 시스템과 리액트 컴포넌트를 결합하는 얇은 레이어다. 아래는 리덕스 공식 문서에 있는 컨테이너 컴포넌트를 설명하는 예제 중 하나다.

리덕스는 connect라는 함수를 제공한다. connect는 이름을 보고 알 수 있듯이, 리액트 컴포넌트와 리덕스 시스템을 결합하는 역할을 수행한다. connect는 첫 번째 호출에서 두 개의 함수를 인자로 받는다.

mapStateToProps와 mapDispatchToProps다. 함수 이름이 매우 직관적이라, 보기만 해도 어떤 일을 하는 녀석인지 바로 알 수 있다.

mapStateToProps를 이용하면 스토어에서 전달받은 상태 트리를 컴포넌트에 전달하기 전에 원하는 형태로 가공할 수 있다. 통역가 역할을 수행하는 셈인데, 이 함수 덕에 컴포넌트와 리듀서, 어느 쪽의 상태 구조가 바뀌어도 변경이 미치는 파급을 최소화할 수 있다.

리덕스처럼 이벤트 통지를 직접 사용자가 제어하지 않는 단일 스토어 방식은 컴포넌트가 구독할 스토어의 이벤트를 직접 지정할 수 없다는 단점을 가지고 있다. 이로 인해 컴포넌트는 자신의 활동과 상관없는 이벤트를 구독해야 한다. 결국 하위 컴포넌트에 불필요한 조정(reconcilation) 프로세스를 발생시켜 이 결과로 애플리케이션의 성능을 저하시킬 가능성이 높아진다.

이 점이 마음에 걸린다면 reseletor 같은 모듈을 이용하면 좋다. 이전 상태를 캐시하고 있다가 새로운 상태가 들어오면 변경 여부를 확인하여 변경이 있을 때만 하위 컴포넌트로 상태를 전파하는 문지기 역할을 하는 모듈이다. 리듀서의 상태를 불변(Immutable)하게 관리하면 이 지점에서 성능상 이점을 얻을 수 있다.

mapStateToProps가 스토어에서 리액트 컴포넌트로 들어가는 통로라면, mapDispatchToProps는 반대로 리액트 컴포넌트에서 스토어로 들어가는 통로다. mapDispatchToProps는 리액트 컴포넌트에서 발생한 이벤트를 액션과 결합하여 스토어로 전달한다. 스토어로 전달한 액션은 리듀서로 넘어가 전역 상태를 변경한다.

리덕스는 mapDispatchToProps가 반환하는 객체를 하위 컴포넌트에 props로 전달한다. 결국 이는 애플리케이션의 요구 사항을 하위 컴포넌트에 콜백으로 전달하는 셈이다. 구글에서 예제를 찾아봤더니 리액트와 리덕스를 결합하는 방식을 크게 두 가지로 압축할 수 있었다.

  1. 이벤트(onClick, onChange, onUpdate…) 기반으로 결합
  2. 요구 사항을 수행하는 메서드(toggleTodo, updateVideo…)를 전달하여 결합

1번은 컨테이너에서 react 컴포넌트로 이벤트 콜백 함수를 내려주면, 컴포넌트는 내부에서 발생하는 사건을 이벤트로 컨테이너에게 알려준다. 컨테이너가 발생한 이벤트를 해석하여 수행할 동작을 결정하는방식이다.

2번은 컨테이너에서 dispatch와 액션을 결합한 행위를 조합하여 props로 내려주면, 리액트 컴포넌트가어떤 행위를 어떤 시점에 실행할지 알아서 결정하는 방식이다.

위의 예제 코드를 보면 무슨 말장난인가 싶다. 단순하게 함수 이름 짓는 방식의 차이 정도로 보이기 때문이다. 맞다. 지금 정도의 코드라면 큰 문제가 없고 이 둘 간의 차이는 무시할만하다.문제는 요구사항 변경이 일어났을 때다.

요구사항이 바뀌어서 할 일을 클릭하면 필터를 변경(setVisibilityFilter)하고 할 일을 토글(toggleTodo) 해야 한다고 가정해보자. 첫 번째 방식의 경우, 아래와 같이 코드를 수정하면 하위 컴포넌트에서는 특별히 다른 처리를 할 필요가 없다. 이벤트 기반으로 props를 구성해서 하위 컴포넌트와 컨테이너를 결합했기 때문에, 컨테이너가 UI에서 발생한 이벤트를 해석하여 적당한 대상에게 전달하는 책임을 지는 것이 자연스럽다.

이와는 달리 두 번째 방식은 props가 행위를 의미하는 이름을 가지고 있다 보니, 아래의 코드처럼 컨테이너로부터 props로 전달받은 행위를 하위 컴포넌트 내부에서 조합하는 형태로 처리하는 유혹에 빠지기 쉽다. 이는 곧 사용자 인터랙션으로 발생한 이벤트를 해석하는 책임을 컴포넌트 내부에서 지는 모양새다.

이제 컴포넌트가 시스템의 요구 사항과 강한 의존성을 갖게 돼 유연성이 떨어져 버렸다. 아직까지 심각한 수준이 아니지만 이런 게 하나둘 쌓이다 보면 걷잡을 수 없다. 물론 아래처럼 첫 번째 방식과 동일하게 해결할 수도 있으나, 함수나 변수의 이름은 알게 모르게 개발자의 생각에 큰 영향을 미친다. 참여 구성원이 많은 프로젝트일수록 코드에 작성자의 의도를 잘 담아내는 일이 중요하다.

여기에서 이야기하고자 하는 핵심은, 어떤 방법을 사용하든 UI에서 발생하는 인터랙션을 해석하여 어떤 액션과 결합할 것인지를 결정하는 책임은 컨테이너에 맡겨야 한다는 점이다. 그래야 리액트 컴포넌트와 리덕스 시스템의 결합도를 낮출 수 있다.

리덕스에 레이어 설계 더하기

MVC 패턴을 설계에 도입해 본 사람이라면 ‘모델, 뷰, 컨트롤러’에 애플리케이션의 모든 관심사를 담을 수 없다는 사실을 잘 알 것이다. 큰 시스템에서 MVC의 각 구성요소는 전체 애플리케이션을 구성하는 레이어 중 어딘가에 위치하는 좀 더 작은 단위의 레이어 또는 객체일 뿐, 그 자체가 전체 설계를 의미하지 않는다. 리덕스도 마찬가지다. 플럭스 아키텍처에 기반을 둔 리덕스가 안내하는 구성요소들에 모든 책임을 담을 수 있을까? 그렇지 않다. 리듀서의 본질을 정리했고, 상태 트리 설계 규칙을 세웠으며, 지역 상태와 전역 상태 구분 규칙을 만들었지만 여전히 리덕스 안에 담을 수 없는 모호한 책임이 있다.

비동기 XHR 요청을 예로 들어보자. 리듀서는 XHR 요청이 위치할 자리가 아니라는 건 분명하다. 그렇다면 어디에 있어야 할까? 컨테이너? 컴포넌트? 액션? 미들웨어? 외부에서 데이터를 가져와 개발 중인 시스템이 해석할 수 있는 포맷으로 데이터를 변환하는 전처리는 어디에서 해야 할까? 여러 액션을 하나의  요청 단위로 묶어야 한다면, 이런 처리는 어디에서 하는 게 적절할까?

개발을 진행하면서 새로운 책임이 계속 등장하는데, 그때마다 이 녀석들을 어디에 둬야 할지를 두고 긴 논쟁이 벌어졌다. 우리가 얻은 결론이 하나 있다면, 어디에 둬도 장단점이 있고 어색하지 않다는 것이다. 다만 비슷한 책임을 수행하는 녀석들을 서로 근접한 곳에 일관성 있게 모아둘 규칙이 필요했다.

그래서 레이어 설계를 정리했다. 도메인 주도 설계(Domain Driven Design)를 참고했지만 어디까지나 지침으로 받아들였다. 레거시와 리덕스가 놓인 상황을 고려해서 우리 식의 해석을 보탰다. 레이어를 합의하고, 컨테이너, 액션, 리듀서의 역할을 정리했다. 이제 새로운 책임이 등장하면 앞에서 정한 규칙에 맞춰 소속을 결정한다. 다만, 그 조차도 모호할 때가 종종 있다는 건 함정이지만, 큰 틀은 마련한 셈이다.

SE3-Mobile Layer Archictecture - Untitled Page각 레이어의 정체성은 아래와 같이 정리했다.

UI 레이어

UI 레이어는 사용자에게 정보를 보여준다. 지역 상태와 관련한 사용자의 요청을 해석하여 상위 컴포넌트 혹은 컨테이너로 전달한다. 컨테이너는 UI를 리덕스 시스템과 연결하는 연결 고리 역할을 한다.

애플리케이션 레이어

애플리케이션의 활동을 조율하는 얇은 레이어 UI 레이어에서 넘어온 사용자 요청을 해석하여 적절한 레이어로 전달한다. 어떤 상태도 직접 보관하거나 관리하지 않는다. 시스템에 사이드 이펙트를 만드는 외부와의 커뮤니케이션 요청을 처리하는 로직은 애플리케이션 레이어에 위임한다. 사용자의 요청을 해석하는 로직이 복잡하거나, 외부에서 넘겨받은 데이터를 가공해야 하는 등의 복잡한 연산을 수행해야 한다면, 관련 로직을 캡슐화하여 서비스 레이어에 위임할 수 있다.

서비스 레이어

XHR을 이용한 외부 데이터 요청, 외부 데이터 변환 같은 애플리케이션 레이어의 임무 수행을 지원하는 모듈이 위치한다. 리듀서에서 사이드 이펙트를 불러오는 외부 데이터 요청을 금지하는 리덕스의 특성상, 외부의 리소스를 요청하는 처리를 이 레이어에서 담당한다.

도메인 레이어

도메인 정보를 가지고 있으며, 생성, 변경, 삭제 등을 담당한다. 전역 상태를 관리하고 처리하며, 처리한 결과를 통지한다. 업무 규칙을 관리하며 전역 상태를 처리할 때 적절한 업무 규칙을 적용한다. 리덕스의 특성상 영속성과 관련한 책임은 도메인 레이어에서 수행하지 않는다.

인프라스트럭쳐 레이어

다른 레이어 모두를 지원하는 라이브러리로 동작한다. 레이어 간의 통신을 제공하고 전역 상태의 영속성을 책임진다. 사용자 인터페이스 레이어에서 사용하는 내/외부 라이브러리를 포함한다.

리듀서와 액션, 단위 테스트 디자인

리액트를 이용할 때 가장 마음에 들었던 점은 테스트가 간결해진다는 부분이다. 다만 이전 플럭스 아키텍처를 이용할 때는 스토어를 테스트하는 코드가 좀 복잡해서 아쉬웠었는데 리덕스는 리듀서로 인해 이 부분에 대한 테스트도 쉬운 편이다. 하지만 두 가지 좀 난해한 부분이 있었다. 아래 내용은 이 부분에 대한 고민의 기록이다.

처음에는 리듀서와 액션의 테스트를 따로 작성했었다. 하지만 작성하다 보니 뭔가 비효율적이라는 생각을 지울 수가 없었는데 그때 고민했던 포인트는 다음과 같다.

  • 액션의 행위는 너무 단순하여 테스트로 얻을 수 있는 이점이 매우 적다(특히 순수한 액션)
  • 액션은 결국 리듀서와 결합해야 하고, 액션이 반환하는 값 자체보다 리듀서가 액션이 전달한 요청을 제대로 처리하고 있는지가 더 중요하다.
  • 리듀서의 행위를 테스트하려면 어쨌든 액션 만드는 페이로드가 필요하다. 더미로 만들 수 있지만 이 경우 실제 액션이 생성하는 페이로드와 더미 페이로드 간의 동기화 문제가 생겨 테스트를 취약(fragile) 하게 만든다.

그래서 리듀서와 액션의 테스트를 하나로 통합했다. 리듀서를 테스트하면 액션은 자연스레 검증된다.

액션을 중첩해서 수행한다거나, 비동기 액션 처리를 위해서 redux-thunk라는 모듈을 사용하는데 이때 중첩 액션을 수행하는 코드는 고차 함수를 이용하여 내부에 함수 클로저를 만드는 방식이다 보니 위에서 보여준 예제의 방식으로는 테스트가 어렵다. 말이 좀 복잡한데 아래의 코드를 보면 redux-thunk가 하위 액션을 어떻게 중첩하는지 알 수 있다.

위의 코드에서 routeBybuttonName -> deleteComponentWithFocus -> deleteComponent에 최종 도착했을 때 최종적으로 반환한 액션이 { type: DELETE_COMPONENT, compId: compId }라는 걸 어떻게 검증해야 할까? sinon을 이용하면 이렇게 테스트할 수는 있다.

하지만 이 코드를 읽고 fakeDispatcher.args[1][0]이 무엇을 의미하는지 단박에 알아차릴 사람이 있을까? when 절의 복잡해서 도대체 무엇을 하고 싶은 건지 이해할 수 없다. 내가 작성했지만 나도 모른다.  이조차도 어려운데, 리듀서와 결합해버리면 더 복잡해진다.

이 문제는 redux-mock-store를 도입해서 해결할 수 있다. 아래의 코드는 위에 있는 테스트 코드를 redux-mock-store를 이용해서 개선한 코드다. 테스트의 가독성이 훨씬 높아졌다.

가짜 스토어 객체를 만들어서 수행한 액션을 히스토리로 기록해 뒀다가 단언으로 검증하는 방식이다.

끝으로…

리덕스로 UI를 개발하다 보면 컴포넌트에서 사용자의 요청을 이벤트로 받아 액션을 스토어로 전달해 리듀서에 있는 상태를 변경한 다음에, 변경한 결과를 받아서 다시 화면을 렌더링 하는 과정이 지나치게 번거롭게 느껴질 때가 있다. 이건 리덕스의 문제라기보다는 애플리케이션을 구조화했을 때 발생하는 문제다. 단방향 데이터 흐름을 강제하는 플럭스 아키텍처 하에서는 이 문제가 좀 더 크게 느껴진다. 그래서 간단한 위젯이나 페이지 내의 인터랙션 처리는 그냥 jQuery로 뚝딱 만드는 게 더 생산적일 수 있다.

그럼에도 이런 번거로운 과정을 견뎌가면서 애플리케이션을 구조화하려고 애쓰는 이유는, 시스템이 복잡해졌을 때 복잡도를 최소화하고 변경이 미치는 파급효과를 최소화하기 위해서다. 당연한 이야기겠지만, 단순히 어떤 프레임워크나 설계 패턴을 도입했다고 해서 시스템의 복잡도가 바로 줄어들지는 않는다. 프로젝트에 참여하는 구성원들이 사용하는 도구의 기저에 깔려있는 철학을 잘 이해해야 하고, 이런 이해를 바탕으로 설계가 해결하지 못하는 사각에 있는 요소들을 찾아서 일관된 방향으로 코드가 자랄 수 있게 또 다른 규칙을 합의해야 한다. 일치한 줄 알았던 서로의 생각이, 훗날 동상이몽이었던 걸로 드러나 허탈해하는 경우는 아주 흔한 일이다. 그래서 꾸준히 생각을 주고받을 수 있는 코드 리뷰 같은 장치가 필요하다. 결국 ‘좋은 설계를 지탱하는 한 축은 원활한 커뮤니케이션’이 아닐까.

오늘 맞다고 생각했던 것들이 내일 뒤집히는 일이 부지기수라 이 글에 적은 생각의 유통기한이 얼마일지는 모르겠다. 좋은 생각이 떠오를 때마다 계속 업데이트를 해야지.


'Web' 카테고리의 다른 글

[ 펌] Webpack  (0) 2016.11.09
Redux 인터넷 강의!  (0) 2016.11.09
JavaScript, React, Flux, Redux 개념 정리.  (0) 2016.11.09
React-Redux 데이터 흐름 개인 종이 정리..  (0) 2016.11.09
React - Flux , Redux 개념 및 차이점 정리  (0) 2016.11.08
블로그 이미지

kuku_dass

,

Web Study.docx


(파일참고)

개념 정리(Javascript, react, flux, redux)

 

JavaScript, Node.js 의 개념

l  JavaScript : Web 에서만 동작할 수 있었던 언어이다. Web browser 에서 동작하는 프로그램을 개발하기 위한 언어였다. 구글의 GMAIL, GMAP을 시작으로 Javascript로 고성능 Web application을 만들 수 있음을 깨닫게 된다.

-       구글은 Chrome 이라는 브라우저를 만들면서, 브라우저의 성능을 높이기 위해서 자바스크립트 엔진을 직접 개발 – V8 (오픈소스로 개방함)

-       V8엔진을 그대로 가져다가 내가 만드는 시스템에서 활용하면 Javascript를 웹이 아닌 다른 곳에서 빠른 고성능으로 Application을 만들 수 있게 됨.

-       위와 같은 이유로 자바스크립트로는 웹에 국한되지 않고, ‘탈웹화하기 시작한다.

 

l  Node.js : 서버 쪽에서 동작하는 Web Application을 만들 수 있도록 해준다.

n  구글의 V8 자바스크립트 엔진을 바탕으로 한 개발 프레임워크 이다.

n  자바스크립트로 Node.js 코드를 작성하고 나면, 이 코드가 실행될 수 있게 V8이 기계어로 컴파일 한다.

-       V8 자바스크립트 엔진 + event-driven programming + non-blocking IO = Node.js

-       웹 브라우저가 아닌 서버 쪽에서 자바스크립트로 개발할 수 있도록 해준다.

n  장점 :

u  Javascript end-to-end : 클라이언트 개발자와 서버 개발자가 동일한 언어를 사용하게 된다.

u  Event-driven : Node.js는 웹 요청을 처리하는데 특유의 로직을 사용한다.
웹 요청 처리를 위해 여러 스레드가 기다리도록 만드는 대신, 기본 이벤트 모델을 이용해서 웹 요청을 동일한 스레드에서 처리한다. 기존 웹 서버들보다 효율적인 방식으로 동작할 수 있도록 한다.

u  Extensibility : 새로운 기능의 추가가 적극적이고 빠르다. (개발자들이 각자 더 좋은 기능을 검증하고 추가함.. openSource… )

u  Fast Implementation : 설치가 쉽고 금방 웹 서버가 동작하도록 만들 수 있다.

 

l  Web 에서 동작하는 JavaScript  VS  Node.js 에서 동작하는 JavaScript ??

n  위에서 JavaScript는 한국어,  Web, node.js는 병원, 법원으로 구분. 병원 법원에서 쓰는 용어 등은 서로 다름. 그러나 문법이나 언어 자체는 한국어를 씀.. 즉 같은 언어를 쓰지만, 병원 법원에서 쓰이는 용어(함수, API 이름 등등)를 알아야, 활용할 수 있다.!

l  JavaScript의 의미는 아래의 두 가지 의미로 보통 사용된다.

n  Language : JavaScript (한국어)

n  Runtime (자바스크립트가 동작하는 환경) : Web Brower, Nodejs (병원, 법원..)

l  Web browser(Client) , Web Server : 양 쪽 모두 JavaScript를 써서 개발이 가능해진다.
즉 하나의 언어로 하나의 Application을 개발 할 수 있다. (원래 Server 쪽은 흔히 Ruby, Java, PHP 등의 웹 서버 프로그래밍을 위해 쓰이는 언어들이 있었다.)
위와 같은 이유로 우리는 JavaScript NodeJs 기술을 배운다.

l  나만의 이해하기 쉬운 예) – 삭제예정..틀린 예인 듯.. _..

상황) 한국어로 말을 하면 일련의 과정을 거쳐 대형 화면에 시각적으로 보이는 시스템이 있다고 하자. ‘언어 -> 번역기 -> 화면에 출력이러한 과정을 거친다.

번역기는 한국어를 화면에 출력해줄 수 있도록 번역해준다.

 

 번역기는 Web Browser가 있고, Node.Js가 있다. Web Browser에서만 쓰이던 V8 엔진을 Node.js도 가지게 되면서, Server에서도 javaScript를 사용할 수 있다.

 결국 Client가 아닌 Server에서도 Node.js 환경에서 javascript로 구현을 하면, 내부적으로 V8엔진을 통해 해당 javaScript 내용을 빠르고 효과적으로 수행하여, 결과를 Browser에게 전달 할 수 있다.

 

 

 

 

모듈과 NPM

 

 Application 에서 부품으로 사용할 로직인 모듈에 대해서 알아보고 모듈을 편리하게 관리하는 기술인 NPM을 사용하는 기본적인 방법 정리.

 

Module

l  Node.js에서 Server를 개발하기 쉽도록 API를 제공해주고 있다.

n  Ex) const http = require(‘http’); require를 함수로 http 모듈을 불러오는 코드이다.

l  Module을 불러다 쓰고 싶으면 ‘require’ 키워드를 활용한다.

l  기본적인 Module Node.js 에서 기본적으로 제공한다.

l  https://nodejs.org/dist/latest-v6.x/docs/api/

NPM : Node Package Manager (Node 세계의 앱 스토어)

l  Module의 설치 / 삭제 / 업그레이드 / 의존성 관리

l  다른 사람이 만든 Module을 우리 프로젝트에 가져와서 사용하는 방법을 살펴보자.

l  좋은 Module을 잘 활용하는 것도 중요하다.

l  Npmjs.com 에 가면, 다른 사람들이 등록해 놓은 Module들을 다운로드 받을 수 있고 그 Module에 대한 Docs 를 볼 수 있다.

l  NPM에 올라와 있는 Module들은 크게 두 가지 유형으로 존재하며, 하나는 독립적으로 실행 될 수  있는 프로그램, 또 다른 하나는 다른 프로그램에 부품처럼 들어가서 사용 되는 형태이며, 다운로드 받을 때, -g 옵션으로 구분하여 받을 수 있다. –g Global 약자로 독립적으로 실행 가능한 프로그램을 받고자 할 때 –g를 붙여서 npm install 한다.

-       Npm install module_name –g : 전역적으로 사용가능한 즉 독립적으로 실행될 수 있는 형태로 module을 받겠다.

-       Npm install module_name : 다른 패키지에 포함되어서 사용될 부품 형태로 다운로드 받겠다.

-       Npm install module_name –save : 패키지에 dependencies에 추가하여, pacakage.json 으로 관리하겠다.

-       Npm init : Npm install 을 하기위해서는 현재 프로젝트 폴더를 npm의 패키지로 초기화를 시켜야 한다. 이때 쓰기 위한 명령어이다.

 

l  Ex) uglify module을 통해서, 소스코드를 위한 가독성 코드 등을 삭제한 필수 코드만 남기게 된다네트워크에서는 보내야 하는 데이터를 최소화 해야 한다. (다 비용이며, 공백도 실제로는 다 데이터이다…_) 보통 uglify minify 와 같은 의미로 보통 코드를 최소화 한 경우, Pretty.min.js 로 저장해준다. (관습적으로 이름을 min 을 붙여 만들어 준다. 의미는 pretty 파일을 최소화 한 js 파일이다라는 의미이다.)

l  NPM 사이트에서 하나의 Module을 부품으로 우리 프로젝트에서 사용하기 위해서 underscore 라는 이름의 Module을 사용하고자 한다고 하자.
npm install
을 먼저 진행하기 전에, npm 에서 받는 module 처럼 우리 프로젝트도 npm에 패키지로 등록을 해줘야 한다. 이때의 작업을 하기 위해선
npm init 
이라는 명령어를 우리 프로젝트 폴더에서 해줘야 한다.

n  : npm init
Name, version , .. , entry point : …., git repository (
우리의 프로젝트를 git 에 올려놓을거면 git repository 써라.. 안써도 된다.)
다 입력하고 나면, 우리 프로젝트 내에 package.json 파일이 생선된다.
 
위와 같은 작업으로 우리의 프로젝트는 npm의 패키지로 등록이 되었다.
 
추후에 우리의 프로젝트를 npm 에 업로드 하여, 쉽게 설치할 수 있도록 초석을 닦아 두었다.
 
여기까지 하면, 다른 사람의 module을 가져다 쓸 수 있는 준비가 완료되었다.

n  이제 npm install module_name 을 입력하면, module 받아지고, 우리의 프로젝트에 자동으로 추가가 된다. 프로젝트에 가보면 node_modules 폴더가 새롭게 생성되고 그 아래 module name의 폴더가 하나 추가되어 코드가 받아져 있다.

n  여기서 npm install module_name –save    라고 쳐주면, package.json 파일에 dependencies 내용이 추가 된다. 즉 우리 프로젝트가 module_name 이라는 module xx 버전에 의존한다. 라고 표기 해준다.

dependencies
내용이 있으면, package.json 파일만 있으면 필요한 module 들을 쉽게 받아서 사용하게 될 수 있다.

결론 : module을 설치할 때,  --save 를 붙여주는 것이 좋다. (일시적으로 사용하는 , 소스코드에 추가할건 아니고, 잠시 코딩중에만 넣고자 하는 module이라면? –save 없이 install을 하고, 꼭 필요한 module이라면 –save를 붙여줘야 한다.

콜백 함수

l  다른 함수의 전달인자로 전달이 된다.

l  우리가 만든 함수이지만, 내가 직접 호출하는 함수가 아니다. 누군가에 의해 호출 당할 함수 이다.

l  Function b(v1,v2){}; A.sort(b) : 여기서 b가 콜백 함수 이다.

동기와 비동기 프로그래밍

l  NodeJS는 기본적으로 Event driven을 중심 개념으로 활용하기 때문에, 기본적으로 제공하는 함수들은 async로 한다. () filesystem.readFile() , readFileSync() 두 개가 존재하는데, 권장은 ReadFile() 사용이다. (Async)

l  Node js Single Thread로 동작한다. 그렇기 때문에 Async에 대해 정확한 이해가 필요하다.

Express

l   

 

 

 

설치

 

Node.js 설치 및 실행

l  Nodejs.org -> Download

Atom Editor 설치

l  https://atom.io/

l  소개 : https://opentutorials.org/module/1579

l  특징

n  무료

n  플러그인으로 기능을 쉽게 확장 할 수 있다.

n  HTMl , CSS, JavaScript와 같은 웹 기술로 화면을 구현하여 웹페이지를 편집하듯이 UI를 제어할 수 있는 특징이 있다.

NPM (Node Package Manager) : Node.js 설치하면 자동으로 설치된다.

l  http://www.Npmjs.com

Module 설치 , Proxy 설정 문제

l  npm ERR! network read ECONNRESET

n  npm config set proxy http://168.219.61.252:8080

n  npm config set https-proxy http://168.219.61.252:8080

n  npm config set strict-ssl false

 

 

 

 

 

React : 페이스북에서 개발한 유저인터페이스 라이브러리이다.

-       개발자로 하여금 재사용 가능한 UI 생성할 있게 해준다.

-       Virtual DOM이라는 개념을 사용하여 상태의 변화에 따라, 선택적으로 유저 인터페이스를 렌더링 한다. 최소한의 DOM 처리로 컴포넌트들을 업데이트 있게 해준다.

 

Sample Code

import React from 'react';

 

function HelloWorld () {

  return (

    <h1>Hello World!</h1>

  );

}

 

export default HelloWorld;

 

 

1.     Import React from ‘react’ (ES6 문법)

A.     코드는 실제로는 var React = require(‘react’) 같은 의미이다.(ES5 문법)

B.     이렇게 모듈을 require 하는 것은 Node.js 문법이다. (서버 개발 사용하는..) 클라이언트 쪽에선 보통 html 태그에서 script 통하여 파일들을 불러오지 require 쓰지 않고, 지원도 하지 않는다.
 
하지만, webpack 이라는 도구를 사용하여 마치 node.js 에서 require 하는 것과 같이 모듈을 불러올 있게 하는 것이다. Webpack 이렇게 import( or require) 모듈들을 불러와서 파일로 합쳐준다. (c에서 include 처럼.. )
 
작업을 번들링(Bundling) 이라고 한다.

2.     Return (<h1>HelloWorld</h1>)

A.     이런식으로 HTML 같은 코드가 ‘ , “ 없이 그냥 쓰이고 있는데, 코드는 JSX 코드이다.

B.     코드는 webpack 에서 번들링 과정을 거치면서 webpack에서 사용하는 babel-loader 통해 Javascript 변환된다. 아래는 JSX babel-loader 의해 변환된 JavaScript 모습

3.  return React.createElement(

4.      "h1",

5.      null,

6.      "Hello World!"

7.    );

C. export default HelloWorld : 다른 곳에서 import from ‘HelloWorld’ 같이 import 있도록 해준다.

 

Redux

 

Redux : Flux 개선형. Flux 같이 단방향 데이터 흐름을 보장하여 보다 예측하기 쉬운 구조를 개발자에게 제공한다. 점에서는 Flux 동일하다. Redux 개발한 개발자는 아래와 같이 Redux Flux 차이점을 서술 하고 있다.

(http://bestalign.github.io/2015/10/26/cartoon-intro-to-redux/)

Flux에서 몇몇 부분을 조금 바꾸면 나은 개발자 도구를 있으면서도 Flux 같은 예측 가능성을 가질 있다는 것을 발견했다.

 

 자세히 보면, 결국 예측 가능성을 가질 있는게 좋아진게 아니다. Flux 같은 수준의 예측 가능성이다. 같다고보면된다. 그러면 차이점은? 나은 개발자 도구. 개발하면서 개발을 편하게 해주는 여러 도구를 사용할 있기 때문에, 개발에 편하다는 것이다.

 결국 개발자 도구의 사용 편의성이 차이점이며, Redux 만든 사람은 아래의 가지를 핵심으로 얘기한다.

 

-      리로딩(hot reloading)

-       시간 여행 디버깅(time travel debugging)

 

앞으로 가지의 차이점을 중점적으로 Flux Redux 차이점을 살펴볼 예정이다.

 

Pure function : 전달인자로 전달받은 값을 덮어쓰지 않고, 단순 계산 등을 통한 값을 return 해준다

 Function squre(x){  return x * x; }

 

Impure function : 네트워크 혹은 데이터베이스를 사용하거나, 전달받은 값을 다른 값으로 덮어써서, 원본의 값을 변형 시키는 함수.

 Function squre(x)

{  updateXInDB(x); return x * x; }

 

 

Reducer : Pure function

 Store action 전달받은 다음, 받은 action root reducer에게 전달한다.

Root reducer 하위의 reducer들이 리턴한 clone state 객체를 combine 최종 app state 값을 store에게 return 한다. , 기존의 previous state 변경하지 않고, clone 새로운 new state 객체를 return 하므로, 우리는 reducer 순수함수. Pure function 이라고 말할 있다.

Smart Component , dumb component:

 Flux 에는 controller view view 있었다. Controller view view들을 가지고 있었으며,

Store 에서 State 바뀔 알려달라고 callback 등록하고 store 통신하는 친구가 바로 Controller view였다. 그래서 store state 바뀌었다고 알려오면, Controller view store로부터 새롭게 업데이트 state 받아와서는 자신에 포함되어 있는 일반 View들에게 prop 형태로 새로 받아온 state 전달하곤 했다.

 

Redux 에서는 Flux에서와 유사하지만, 이름이 다르다.

Smart Component, dumb component : 한국어로는 영민한 컴포넌트와 우직한 컴포넌트!

-       Component 서로 구분되기 위해, Cart Example에서는 Smart Component Container 라는 이름으로 사용하고, dumb component component 사용되었다.

-       .. Container라고 지은 이유는?.. 추측 해봤는데, 일반 view 유사한 Dumb component 들을 포함하고 있는. 가지고 있으면서 관리도 하기 때문에, Container라는 이름을 지은 것이 아닐까 생각해본다

(http://bestalign.github.io/2015/10/26/cartoon-intro-to-redux/)

 

l  Smart Component : Controller view 유사하다. 몇몇 규칙을 따른다.

n  액션 처리를 책임진다. Smart Component 내부적으로 아래 코드 처럼 dumb component 가지고 있는데, dumb component 에서 클릭이 되었을 , checkout 이라는 동작을 해야 한다면, 아래와 같이 smart component(container) dumb component에게, prop 형태로 checkout 함수를 콜백으로만 등록해준다.
 dumb component
입장에서는 어떤 액션을 보낼 필요가 있을 , smart component로부터 전달받은 함수를 단순히 호출만 해준다.

n  render() {

n      const { products, total } = this.props

n   

n      return (

n        <Cart

n          products={products}

n          total={total}

n          onCheckoutClicked={() => this.props.checkout()} />

n      )

n    }

n  Smart Component CSS style 가지고 있지 않다.

n  Smart Component 자신만의 DOM 거의 가지고 있지 않다. 대신 DOM 요소들을 관리하는 dumb component 관리한다.

l  우직한 컴포넌트는 액션에 직접 의존성을 가지지는 않는다. 이는 모든 액션을 props 통해서 넘겨받기 때문이다. 이말인 즉슨, 우직한 컴포넌트는 다른 로직을 가진 다른 애플리케이션에서 재사용될 있다는 뜻이다. 또한 어느정도 보기좋게 할만큼의 CSS style 포함하고 있다 (따로 style props 받아 기본 style 병합시켜서 style 바꿀수도 있다).

 

** Smart Component(Container) 코드를 살펴보면,

mapStateToProps 콜백함수를 정의하고 있고,

export default connect() 라는 코드를 있다. 이유는? 주석으로 적혀 있음.

 

//Smart Component 네트워크에 연결되면서, 새롭게 업데이트되어 전달될 state 가지고

//어떻게 자신이 가진 prop 업데이트 적용할지를 기술한 콜백 함수이다.

 

const mapStateToProps = (state) => {

  return {

    products: getCartProducts(state),

    total: getTotal(state)

  }

}

 

//Root Component 내에 있는 Provider 객체는 레이어 바인딩인데,

// 녀석이 기본적으로 Store State 업데이트가 되었음을 알려주면

// 컴퍼넌트들도 업데이트 있도록 네트워크를 생성한다.

 

//Smart Component Provider 생성해놓은 네트워크에 자신을 연결한다. Connect() 이용.

//이렇게 하면, 상태 업데이트를 받을 있게 된다.

 

export default connect(

  mapStateToProps,

  { checkout }

)(CartContainer)

 

:   레이어 바인딩이 생성해놓은 네트워크 (스토어의 state 업데이트를 받아 있도록.. 만든.. ) CartContainer 라는 이름의 Smart component 연결하라.!

 

 Store로부터 새로운 State 받아오면, Cart Container Prop 업데이트 건데, 업데이트를 위해 사용할 콜백함수는 ‘mapStateToProps’ 이다.!

 

 라고 네트워크에 연결하면서 설정해놓는다.

 

 이제 레이어 바인딩 Provider 객체 덕분에 Store state 변경 내용을 Smart Component 전달받게 되고, smart component 자신의 prop 업데이트 , 자신이 소유하고 있는 dumb component 들에게 prop 이어서 전달하므로, 각각의 view. dumb component 들은 새롭게 전달받은 prop 데이터를 이용해서 view 업데이트 한다.

 

 

 

 

View Layer Binding ( 레이어 바인딩)

 Store View 연결하기 위해서는 Redux 도움이 필요하다.

  둘을 함께 묶어줄 무언가가 필요한데, 이걸 해결해주는 것이 바로 레이어 바인딩이다.

 React 사용한다면 react-redux 그것이다.

 (Redux shopping cart 예제에서는 react-redux 로부터 얻은 Provider 라는 객체를 사용한다.)

 

레이어 바인딩은 모든 컴퍼넌트를 스토어에 연결하는 역할을 한다.

<Provider store={store}>

    <App />

  </Provider>

 

Provider 레이어 바인딩이며, store 아래 <App /> 객체를 연결해준다.

다른 말로, App 내부적으로 Container(smart component) , dumb component . View 들을 모두 들고 있는 객체이므로, Store 모든 컴퍼넌트들을 연결하는 역할을 한다고 있다.

 

레이어 바인딩은 개의 컨셉을 가지고 있다: (Provier 객체가 설명에 등장한다.!!)

1.       공급 컴포넌트(provider component): 컴포넌트 트리를 감싸는 컴포넌트이다connect() 이용해 루트 컴포넌트 밑의 컴포넌트들이 스토어에 연결되기 쉽게 만들어준다.

2.       connect(): react-redux 제공하는 함수이다. 컴포넌트가 애플리케이션 상태 업데이트를 받고 싶으면 connect() 이용해서 컴포넌트를 감싸주면 된다. 그러면 connect() 셀렉터(select) 이용해서 필요한 모든 연결을 만들어준다.

3.       셀렉터(selector): 직접 만들어야 하는 함수이다. 애플리케이션 상태 안의 어느 부분이 컴포넌트에 props로써 필요한 것인지 지정한다.

 

 

Root component:

 루트 컴포넌트는 모든 팀이 일을 하도록 하는 임무를 가진다. 스토어를 생성하고 무슨 리듀서를 사용할지 알려주며 레이어 바인딩과 뷰를 불러온다.

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

 정리 : 루트 컴포넌트는 공급 컴포넌트로 서브 컴포넌트를 감싸고 스토어와 공급 컴포넌트 사이를 연결한다. 

render(

  <Provider store={store}>

    <App />

  </Provider>,

  document.getElementById('root')

)

 

 우리의 shopping cart example 에서는 index.js 파일이 root component 이다.

 

내용까지를 가지고, 아래의 내용을 읽어보면 것이다. 아래의 내용은 모두 http://bestalign.github.io/2015/10/26/cartoon-intro-to-redux/ 사이트 내용을 그대로 복사 것이며, 내용을 이해하고 보거나, 아래 내용을 읽고 내용을 읽으면 이해하는데 도움이 것이다.

 

어떻게 함께 동작하는가

애플리케이션을 생성하기 위해 어떻게 캐릭터들이 함께 일하는지 살펴보자.

준비(the setup)

준비 단계에서는 애플리케이션의 여러 부분이 모두 함께 연결된다.

1.     스토어를 준비한다. 루트 컴포넌트는 createStore() 이용해서 스토어를 생성하고 무슨 리듀서를 사용할지 알려준다. 루트 컴포넌트는 이미 필요한 모든 리듀서를 가지고 있다combineReducers() 이용해서 다수의 리듀서를 하나로 묶는다.  

2.     스토어와 컴포넌트 사이의 커뮤니케이션을 준비한다. 루트 컴포넌트는 공급 컴포넌트로 서브 컴포넌트를 감싸고 스토어와 공급 컴포넌트 사이를 연결한다

공급 컴포넌트는 기본적으로 컴포넌트를 업데이트하기 위한 네트워크를 생성한다. 영민한 컴포넌트는 connect() 네트워크에 연결한다. 이렇게 상태 업데이트를 받을 있게 만든다.  

3.     액션 콜백(action callback) 준비한다. 우직한 컴포넌트가 액션과 쉽게 일할 있게 하기 위해 영민한 컴포넌트는 bindActionCreators() 액션 콜백을 준비한다. 이렇게 간단히 콜백을 우직한 컴포넌트에 넘겨줄 있다. 액션은 포맷이 바뀐 자동적으로 보내진다.  

데이터 흐름(the data flow)

이제 사용자가 애플리케이션을 사용할 준비가 되었다. 액션을 하나 보내고 데이터 흐름을 확인해보자.

1.     뷰가 액션을 요청한다. 액션 생성자가 포맷을 변환한 돌려준다.

  

2.     bindActionCreators() 준비과정에서 사용되었으면 자동으로 액션이 보내진다. 그게 아니라면 뷰가 직접 액션을 보낸다.

  

3.     스토어가 액션을 받는다. 현재 애플리케이션 상태 트리와 액션을 루트 리듀서에게 보낸다.

  

4.     루트 리듀서는 상태 트리를 조각으로 나눈 알맞은 서브 리듀서로 상태 조각들을 넘겨준다.

  

5.     서브 리듀서는 받은 상태 조각을 복사한 , 복사본을 변경한다. 루트 리듀서에게 변경된 복사본을 돌려준다.

  

6.     모든 서브 리듀서가 변경 상태 조각들을 돌려주면, 루트 리듀서는 상태 조각들을 한데 모아 상태 트리로 만든 스토어로 돌려준다. 스토어는 새로운 상태 트리를 옛날 상태 트리와 바꾼다.

  

7.     스토어는 레이어 바인딩에게 애플리케이션 상태가 변경되었다는 것을 알린다.

  

8.     레이어 바인딩은 스토어에게 새로운 상태를 보내달라고 요청한다.

  

9.     레이어 바인딩은 뷰에게 화면을 업데이트하도록 요청한다.

  

 

 

블로그 이미지

kuku_dass

,


블로그 이미지

kuku_dass

,

세미나자료_FLux.pptx

세미나자료_Redux_151201.pptx

 

'Web' 카테고리의 다른 글

JavaScript, React, Flux, Redux 개념 정리.  (0) 2016.11.09
React-Redux 데이터 흐름 개인 종이 정리..  (0) 2016.11.09
Redux  (0) 2016.11.08
JavaScript , react 등 좋은 강의 사이트  (0) 2016.11.08
React & Flux Data Flow  (0) 2016.11.08
블로그 이미지

kuku_dass

,

Redux

Web 2016. 11. 8. 16:56
http://bestalign.github.io/2015/10/26/cartoon-intro-to-redux/

 

블로그 이미지

kuku_dass

,
https://opentutorials.org/

 

'Web' 카테고리의 다른 글

JavaScript, React, Flux, Redux 개념 정리.  (0) 2016.11.09
React-Redux 데이터 흐름 개인 종이 정리..  (0) 2016.11.09
React - Flux , Redux 개념 및 차이점 정리  (0) 2016.11.08
Redux  (0) 2016.11.08
React & Flux Data Flow  (0) 2016.11.08
블로그 이미지

kuku_dass

,

React & Flux Data Flow

Web 2016. 11. 8. 13:49

 

블로그 이미지

kuku_dass

,