http://reactivex.io/rxjs/manual/tutorial.html 글을 대충 번역해서 쓰는 글입니다.
jsbin/codepen 같은 곳에서 RxJS 라이브러리를 추가한 후 연습해봅시다.
현재 가장 최근 버전은 https://cdnjs.com/libraries/rxjs/5.0.0-rc.1 에서 확인하세요.
jsbin/codepen 같은 곳에서 RxJS 라이브러리를 추가한 후 연습해봅시다.
현재 가장 최근 버전은 https://cdnjs.com/libraries/rxjs/5.0.0-rc.1 에서 확인하세요.
- Observable 로 변환하기
- 하나 혹은 n 개의 값으로부터
Rx.Observable.of('foo', 'bar');
=>foo
=>bar - 배열로부터
Rx.Observable.from([1,2,3]);
=>1
=>2
=>3 - 이벤트로부터
Rx.Observable.fromEvent(document.querySelector('button'), 'click');
=>button을 누를 때 마다 event로 변환 - Promise로부터
Rx.Observable.fromPromise(fetch('/users'));
=>/user 주소의 내용을 fetch한 것을 변환 - 마지막 인자가 콜백인 경우 콜백으로부터
/* node.js */
var exists = Rx.Observable.bindCallback(fs.exists);
exists('file.txt').subscribe(exists => console.log('Does file exist?', exists));
var rename = Rx.Observable.bindNodeCallback(fs.rename);
rename('file.txt', 'else.txt').subscribe(() => console.log('Renamed!')); - Observable 만들기
- 외부로부터 새로운 이벤트를 생성한다.
var myObservable = Rx.Subject.create();
myObservable.subscribe(value => console.log(value));
myObservable.next('foo'); - 내부에서 새로운 이벤트를 생성한다.
var myObservable = Rx.Observable.create(observer => {
observer.next('foo');
setTimeout(() => observer.next('bar'), 1000);
});
myObservable.subscribe(value => console.log(value)); - 어떤 것을 선택할지는 시나리오에 따라 선택할 수 있습니다.
일반적인 Observable은 당신이 시간이 흘러가면서 값을 생성하는 기능을 구현할때 좋습니다.
일례로 websocket 연결 같은 것을 들 수 있다.
Subject를 사용하면 어디서나 새로운 이벤트를 작동할 수 있으며 기존의 Observable과 연결할 수 있습니다. - 흐름(Flow)을 제어하기
// "hello world"라고 쳐봅니다.
var input = Rx.Observable.fromEvent(document.querySelector('input'), 'keypress');
// 2글자 이하로는 필터링합니다
input.filter(event => event.target.value.length > 2)
.subscribe(value => console.log(value)); // "hel"
// 이벤트를 지연시킵니다.
input.delay(200)
.subscribe(value => console.log(value)); // "h" -200ms-> "e" -200ms-> "l" ...
// 매번 200ms 동안 한번만 받아들입니다.
input.throttleTime(200)
.subscribe(value => console.log(value)); // "h" -200ms-> "w"
// 마지막 200ms 후에 발생한 것만 통과시킵니다.
input.debounceTime(200)
.subscribe(value => console.log(value)); // "o" -200ms-> "d"
// 3개의 이벤트만 받습니다.
input.take(3)
.subscribe(value => console.log(value)); // "hel"
// 다른 옵저버블이 이벤트를 발생시킬 때 통과시킵니다.
var stopStream = Rx.Observable.fromEvent(document.querySelector('button'), 'click');
input.takeUntil(stopStream)
.subscribe(value => console.log(value)); // "hello" (click) - 값(Value)을 만들기
// "hello world" 라고 쳐봅니다.
var input = Rx.Observable.fromEvent(document.querySelector('input'), 'keypress');
// 새로운 값을 내보냅니다.
input.map(event => event.target.value)
.subscribe(value => console.log(value)); // "h"
// 값 뽑아내기
input.pluck('target', 'value')
.subscribe(value => console.log(value)); // "h"
// 값을 쌍으로 뽑아내기
input.pluck('target', 'value').pairwise()
.subscribe(value => console.log(value)); // ["h", "e"]
// 중복 값 제거하기
input.pluck('target', 'value').distinct()
.subscribe(value => console.log(value)); // "helo wrd"
// 이전 값과 다를 때만 내보내기
input.pluck('target', 'value').distinctUntilChanged()
.subscribe(value => console.log(value)); // "helo world" - 응용프로그램(Application) 만들기
RxJS는 코드의 오류를 줄이기에 탁월한 도구입니다.
이에 순수함수와 상태 없는(stateless) 함수를 사용합니다.
하지만 응용프로그램들은 상태를 가지고 있습니다(stateful).
그렇다면 우리는 stateless한 RxJS 와 stateful한 응용프로그램을 어떻게 연결할 수 있을까요?
0 이라는 값으로부터 단순한 상태 저장(State Store)을 만들어봅시다.
매번 클릭할 때 마다 우리는 State Store의 값이 증가하길 원합니다.
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
// 카운트의 스트림을 합산(scan-reduce)합니다.
.scan(count => count + 1, 0)
// 매번 그것이 변할 때 마다 요소(element)의 갯수를 설정합니다.
.subscribe(count => document.querySelector('#count').innerHTML = count);
이렇게 RxJS에서 상태를 만들어 보았습니다. 하지만 DOM을 변경하는 것은 마지막 줄에서 일어나는 부작용(Side-effect)입니다. - 상태 저장소(State store)
응용 프로그램에서는 상태를 유지하기를 위해 State Store를 사용합니다. 이들은 프레임워크마다 store나 reducer, model 등 각각 다른 이름으로 불립니다. 그러나 그것들은 단지 객체(Object)일 뿐입니다.
var increaseButton = document.querySelector('#increase');
var increase = Rx.Observable.fromEvent(increaseButton, 'click')
// 우리는 함수에 대입하여 상태를 변하게 합니다.
.map(() => state => Object.assign({}, state, {count: state.count + 1}));
우리가 여기서 하는 것은 클릭 이벤트를 상태 변경 함수에 매핑 하는 것입니다.
그래서 값을 매핑하는 것 대신 함수를 매핑하였습니다.
함수는 State Store 의 상태를 변화시킬 것입니다.
그러면, 실제로 어떻게 변하는지 살펴봅니다.
var increaseButton = document.querySelector('#increase');
var increase = Rx.Observable.fromEvent(increaseButton, 'click')
.map(() => state => Object.assign({}, state, {count: state.count + 1}));
// 우리는 초기 상태를 갖는 객체를 만들었습니다. 새로운 상태 변화가 있을 때 마다
이를 불러내고 상태를 전달합니다.
// 새로운 상태를 반환하고 다음 클릭에 대해 변경할 준비를 합니다.
var state = increase.scan((state, changeFn) => changeFn(state), {count: 0});
우리는 이제 같은 State store를 변경할 더 많은 Observables 를 추가할 수 있습니다.
var increaseButton = document.querySelector('#increase');
var increase = Rx.Observable.fromEvent(increaseButton, 'click')
// 카운트를 증가시키는 함수를 매핑합니다.
.map(() => state => Object.assign({}, state, {count: state.count + 1}));
var decreaseButton = document.querySelector('#decrease');
var decrease = Rx.Observable.fromEvent(decreaseButton, 'click')
// 또한 카운트를 감소시키는 함수도 매핑합니다.
.map(() => state => Object.assign({}, state, {count: state.count - 1}));
var inputElement = document.querySelector('#input');
var input = Rx.Observable.fromEvent(inputElement, 'keypress') // keypress 이벤트로 inpupValue 상태를 만듭니다.
.map(event => state => Object.assign({}, state, {inputValue: event.target.value}));
// 이 세가지 상태를 생성하는 Observables들을 병합(Merge)합니다.
var state = Rx.Observable.merge(
increase,
decrease,
input
).scan((state, changeFn) => changeFn(state), {
count: 0,
inputValue: ''
});
// 상태 변화에 대해 subscribe 하고 DOM을 갱신합니다.
state.subscribe((state) => {
document.querySelector('#count').innerHTML = state.count;
document.querySelector('#hello').innerHTML = 'Hello ' + state.inputValue;
});
// 실제로 상태가 바뀌었을 때를 확인하여 렌더링을 최적화합니다.
var prevState = {};
state.subscribe((state) => {
if (state.count !== prevState.count) {
document.querySelector('#count').innerHTML = state.count;
}
if (state.inputValue !== prevState.inputValue) {
document.querySelector('#hello').innerHTML = 'Hello ' + state.inputValue;
}
prevState = state;
});
원문에는 immutable JS 나 React 와 같이 실제로 응용프로그램 차원에 적용하는 법도 있지만 이 글에선 범위 밖이라 다루지 않습니다.
각자 자신이 사용하는 환경에 맞게 적용해봅시다.
댓글
댓글 쓰기