요즘 개인적으로 관심 있는 주제가 Reactive Programming 이다. 이 Reactive Programming의 구현체(?)에 해당하는 것 중 하나가 바로 RxJS 이다. RxJS는 Angular2에서 사용되고 있다. 조만간 React도 RxJS를 채택할 것이라는 소문(?)도 들린다. 뿐만 아니라, Rx의 Observable은 이미 ES7의 정식 스펙으로 제안이 되어 있기도 하다.
Front-End 뿐만 아니라, Android에서는 Rx가 필수 유틸이 될 정도로 자리를 잡고 있고, 게임 개발에서도 사용하고 있는 것으로 보인다.

학습의 진입장벽

개인적으로 Reactive Programming에 대한 학습 진입장벽이 꽤 큰 영역인것 같다.

왜? 바로 명령형, 절차적 프로그래밍 학습에 대한 관성이 우리에게 체화되었기에 그런 것으로 보인다.

학교나 회사에서 우리가 배우고 사용했던 프로그래밍 방식의 대부분은 명령형, 절차적 프로그래밍이었다. 하지만 이 Reactive Programming의 방식은 데이터의 흐름을 나타내는 것에 그 본질이 있다.
따라서, 행위에 대해 명령을 하는 것이 아니라, 데이터의 흐름을 선언 하는 것이고, 절차적으로 프로그래밍 하는 것이 아니라, 비동기, 병렬 상황에 대해 개발하는 방식이다.
그나마 난 JavaScript 개발자이기에 비동기 상황에 대해서는 익숙하지만, 그래도, 익숙하지 않는 방식이다.
한마디로, 개념의 전환이 쉽지 않은 것 같다. ㅠㅠ

RxJS 정말 필요한가?

그렇다면, Reactive Programming의 구현체(?)인 RxJS가 정말 FrontEnd 영역에서 필요할까?

의문이다.

뭔가 의미론적으로는 굉장한 좋은 녀석 같은데, 정말 좋은지 모르겠다.

그래서 아직은 익숙하지 않는 RxJS를 이용하여, 비교적 이벤트나 비동기 상황을 제어할 필요가 있는 캐로셀(flicking) 컴포넌트를 만들어 봤다.

RxJS 5.0을 사용하여 캐로셀을 만든 예제

Browser API를 사용하여 캐로셀을 만든 예제

캐로셀(flicking)의 동작 방식

비교에 앞서 캐로셀(flicking) 기능 구현을 위해 필요한 핵심 로직만 좀 살펴보자.

  1. touchstart(mousedown) 발생 시
  • 동작하고 있다는 표시(playing)을 한다. 그리고, 현재 선택 한 좌표(start)를 저장한다.
  1. touchmove(mousemove) 발생 시
  • playing 시에만 이벤트가 발생하고, touchstart(mousedown)에서 발생한 좌표를 기준으로 이동한 좌표(distance)를 구한다.
  • 이동한 좌표 만큼 패널을 움직인다.
  1. touchend(mouseup) 발생 시
  • touchstart(mousedown)에서 발생한 좌표를 기준으로 이동한 좌표(distance)를 구한다. 이동한 좌표에 따라 애니메이션으로 판을 움직일지를 결정한다.
  • 애니메이션으로 움직일 필요가 있을 경우, 애니메이션으로 판을 움직이고, playing 여부를 false로 지정한다.
  • 판이 다음이나, 전 판으로 이동시, 순환을 위해 판의 좌표를 변경한다.

위에서 언급한 것처럼 간단한 기능이지만, 이벤트 흐름이 이렇게 동작할 거라고 이해를 해야만 개발을 할 수 있다. 또한 애니메이션 구현도 해야만 한다.

중요한 로직 비교

touchstart - touchmove - touchend 이벤트 처리

RxJS

touchstart이벤트 발생시, do로 작업을 하고, move 이벤트 발생시에는 start좌표와의 차이(distance)의 데이터로 변환하고, 이 데이터의 변경이 있을 경우, _renderMove 함수로 실제 패널을 움직인다. 이 작업은 touchend 이벤트가 끝나는 순간까지 stream을 발생 하고, 이를 계속 반복한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// change$
Rx.Observable.fromEvent(el, EVENTS.start)
.map(getPos)
.do(v => {
this.playing = true;
this.startSubject.next(v);
})
.flatMap(v =>
Rx.Observable.combineLatest(
this.startSubject,
Rx.Observable.fromEvent(el, EVENTS.move).map(getPos),
(s, m) => m - s
)
)
.distinctUntilChanged()
.do(v => this._renderMove(v))
.takeUntil(this.end$)
.repeat()
.subscribe();
Browser API

반면, RxJS를 사용하지 않은 코드는 데이터의 흐름을 코드만 보고 파악하기가 어렵다. 더불어, 별도의 변수(start, distance, …) 값들을 유지하고, onEnd 이벤트 핸들러에서 변수를 초기화 해주는 작업도 해줘야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
this.start = null;
this.distance = null;
this.onStart = el.addEventListener(EVENTS.start, e => {
this.playing = true;
this.start = getPos(e);
});
this.onMove = el.addEventListener(EVENTS.move, e => {
if (this.start === null) return;
let distance = getPos(e) - this.start;
this.distance !== distance && this._renderMove(distance);
this.distance = distance;
});
this.onEnd = el.addEventListener(EVENTS.end, e => {
//...
this.start = null;
this.distance = null;
});

Animation 처리

RxJS

touchend(mouseup) 이벤트가 발생할 때에 end 좌표와 start시의 좌표. 그리고 이동할 거리 등을 기준으로 from, to, duration 값을 계산한다.
계산된 정보는 _crateAnimation$에 전달 되어, 실제 애니메이션이 동작한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_createAnimation$(from, to, duration) {
//...
Rx.Observable.generate(
performance.now(),
x => x <= startTime + duration,
x => performance.now(),
x => (x-startTime)/duration,
Rx.Scheduler.animationFrame
)
.map(p => from + (p * (next ? -distance: distance)))
.concat(Rx.Observable.of(to))
.distinctUntilChanged()
.do(v => this._renderMove(v))
.last()
.do(v => {
v !==0 && this._renderRearrange(next);
this.playing = false;
});
},

Observable.generater를 이용하여 애니메이션 좌표가 발생하고, 이 좌표에 따라, _renderMove에 의해 실제 패널이 이동된다. 이 패널의 이동이 완료되면, 패널 이동 여부에 따라 _renderRearrange에 의해 패널의 위치가 재정의 된다. 지금 느낄지 모르겠지만, 꽤나 선언적이다.

Browser API

RxJS와 마찬가지로 touchend(mouseup) 이벤트가 발생할 때에 end 좌표와 start시의 좌표. 그리고 이동할 거리 등을 기준으로 from, to, duration 값을 계산한다.
계산된 정보는 _runAnimation에 전달 되어, 실제 애니메이션이 동작한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_runAnimation(from, to, duration) {
// ...
let self = this;
let startTime = performance.now();
let p = 0;
let pos;
let beforePos = -1;
(function animate () {
let x = performance.now();
if (x >= startTime + duration) {
self._renderMove(to);
to !==0 && self._renderRearrange(next);
self.playing = false;
return;
}
p = (x-startTime)/duration;
pos = from + (p * (next ? -distance: distance));
(beforePos !== pos) && self._renderMove(pos);
beforePos = pos;
window.requestAnimationFrame(animate);
})();
}

RxJS를 사용하지 않는 다음 코드는 앞의 이벤트 보다는 흐름을 알지 못해도 이해하기 쉬운 반면, 값의 변경 여부 확인이나 중간값 계산을 위한 의미론 적으로 불필요한 변수들(beforePos, p, self,..)을 사용하게 된다.

Front-End에서 RxJS는 필요한가?

우선 코드량은 전체적으로 별차이가 없다. 크롬 Timeline을 통해 애니메이션 프레임을 확인해 본 바로는 속도도 별 차이가 없다. 차이가 있다면, RxJS의 호출 스택이 좀 길다는 것 정도이다.
물론, PC에서 확인해서 그런지는 모르겠지만, 이건 RxJS를 사용하지 말아야하는 이유가 되지는 못할 것 같다.

단지 차이가 있다면 보다 선언적으로 프로그래밍이 된 정도? 일까?
RxJS의 이해도가 있는 사람은 코드를 통해 의미를 찾을수 있다는 장점이 있는 것 같다. 또한, 기본적으로 제공하는 좋은 operator가 있어서 좀 지저분한 변수들이 없어지는 장점도 있다. 하지만, 디버깅면에서는 좀 단점인것 같다. do operator를 통해 확인하는 방법이 최선인것 같다.

지금 상황에서 RxJS 를 쓸거냐고 누군가가 물어본다면…
내 대답은 실시간 채팅이나 실시간 SNS 정도면 모를까. 지금의 웹서비스에 적합할 지는 모르겠다.

솔직히 지금은 내가 내공이 약해서 잘 모르겠다.
지금 짠 코드도 진짜 RxJS를 잘 활용한 것인지도 모르겠다.
암튼 더 관심을 가지고 봐야할 주제인 것 같긴하다 ^^;;

Comment and share

Reactive Programming

이 글은 기존에 잘 정리된 문서를 보고 학습한 결과를 바탕으로 제 기준으로 다시 간단히 정리한 문서입니다.

Reactive Programming 이란?

참조 문서

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

Reactive Programming

Reactive programming is programming with asynchronous data streams.
You can listen to that stream and react accordingly.

  • Reactive Programing은 기본적으로 모든 것을 스트림(stream)으로 본다. 이벤트, ajax call, 등 모든 데이터의 흐름을 시간순서에 의해 전달되어지는 스트림으로 처리한다. 즉, 스트림이란, 시간순서에 의해 전달되어진 값들의 collection 정도로 이해해 보자.
  • 각각의 스트림은 새로 만들어(branch)져서 새로운 스트림이 될 수도 있고, 여러개의 스트림이 합쳐(merge) 질수 도 있다.
  • 스트림은 map, filter과 같은 함수형 메소드를 이용하여, immutable하게 처리할 수 있다.
  • 스트림을 listening 함으로써 데이터의 결과값을 얻는다. 이를 subscribe라고 표현한다.

Observable과 Observer

An observer subscribes to an Observable. An Observable emits items or sends notifications to its observers by calling the observers’ methods.
http://reactivex.io/documentation/observable.html

  • Observable은 observer의 메소드를 호출하면서 item이나 정보등을 호출(emit)하는 역할을 한다. Observer는 onNext, onError, onCompleted의 메소드가 구현되어 있다.
  • Observer는 observable을 subscribe한다. Observer는 Subscriber, watcher, reactor로 불려진다.

그럼 왜 Reactive Programming 인가?

Apps nowadays have an abundancy of real-time events of every kind that enable a highly interactive experience to the user. We need tools for properly dealing with that, and Reactive Programming is an answer.

  • 함수형으로 만들기 때문에, 하나의 함수는 그 역할 자체에 집중할수 있다.

  • Promise의 장점을 극대화할 수 있다.
    Reactive Programming에서 갑자기 Promise를 이야기하는 이유는, RxJS의 Observable이 Promise와 개념적으로 유사하다. 차이가 있다면, Promise는 단 하나의 value를 다룰 수 있지만, Observable은 다수의 value를 다룰 수 있다.

    1
    2
    myObservable.subscribe(successFn, errorFn);
    myPromise.then(successFn, errorFn);

    The Promise is an Observable
    The Observable is not a Promise
    ES7 스펙에 Observable이 제안되어 있지만 현재는 표준이 아니다. 하지만, Promise는 Promises/A+ 표준이다.

    처음 Promise를 접할 때에는 좀 낯설었지만, 실제 구현상의 편리함이나, 로직의 심플함, 비동기 처리를 동기식으로 개발할 수 있는 장점 덕분에, 좀더 알아먹기 쉬운 코딩을 할수 있다. 익숙해지면, Observable은 Promise보다 더 강력하다.

  • Observable은 A steam에 의해 B stream이 영향을 받는 경우, A만 바꿔도 B가 자동으로 바꿀 수 있도록 구성할 수 있어서, 데이터의 동기화를 간편하게 할 수 있다. 이러한 이유는 A와 B stream 사이의 관계를 선언적으로 선언했기 때문에 가능하다. [예제]

RxJS 참조 문서

ReactiveX 공식

http://reactivex.io/

stream 생성 static 메소드

https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/which-static.md

stream operator

https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/which-instance.md

observable api

https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md

operator 다이얼그램

http://rxmarbles.com/

실습한 예제

실습하면서 공부한 예제
http://jsbin.com/pekemu/edit?js,console,output

생각해볼 문제

  • map, filter와 같은 함수를 계속 쓰면, 객체를 계속 만드는 거 아닌가?

    RxJS는 객체를 재활용함. 문제없음. 음하하하

    RxJS and Reactive Programming - Modern Web UI - May 2015 from Ben Lesh

  • RxJS와 Reactive Programing은 같은 건가?

    아니, RxJS는 Reactive Programming에서 시간을 제어할 수 있는 Schedule 기능, 등이 포함되어 있는 라이브러리이다.

Comment and share

  • page 1 of 1
Author's picture

sculove

아내와 아들 그리고 딸밖에 모르는 남편


FrontEnd Developer


대한민국/서울