2021. 1. 16. 18:00ㆍ🔴 FE/RxJS
BehaviorSubject와 ReplaySubject의 차이점을 예제를 통해 알아보려고 한다.
Official Documentation
- BehaviorSubject
A variant of Subject that requires an initial value and emits its current value whenever it is subscribed to.
initial 값을 지정해야 하고, 구독이 됐을 때 현재의 값을 emit 하는 Subject.
- ReplaySubject
A variant of Subject that “replays” or emits old values to new subscribers. It buffers a set number of values and will emit those values immediately to any new subscribers in addition to emitting new values to existing subscribers.
새로운 구독이 일어났을 때 이전의 값들을 emit하는, 즉 '리플레이'하는 Subject.
정해진 수의 값들을 가지고 있다가 새로운 구독자들이 생겼을 때 바로 그 값들을 새로운 구독자와 기존 구독자에게 emit 한다.
( 해당 글 일부분을 번역하였습니다. )
BehaviorSubject
현재의 값을 저장하고 있는 것이 특징인 Subject, 따라서 우리는 항상 가장 최근에 emit 된 값을 가져올 수 있다.
가장 최신의 값을 가져오는 방법은 두 가지가 있다 :
- BehaviorSubject의 .value 속성을 참고하기
- BehaviorSubject을 구독하기
만약 BehaviorSubject을 구독한다면 BehaviorSubject은 구독자에게 바로 최신 값을 emit 해준다.
BehaviorSubject가 값을 저장하고 나고 한참 뒤에 구독을 해도 최신 값을 받을 수 있다.
import * as Rx from "rxjs";
const subject = new Rx.BehaviorSubject();
// subscriber 1
subject.subscribe((data) => {
console.log('Subscriber A:', data);
});
subject.next(Math.random());
subject.next(Math.random());
// subscriber 2
subject.subscribe((data) => {
console.log('Subscriber B:', data);
});
subject.next(Math.random());
console.log(subject.value)
// output
// Subscriber A: 0.24957144215097515
// Subscriber A: 0.8751123892486292
// Subscriber B: 0.8751123892486292
// Subscriber A: 0.1901322109907977
// Subscriber B: 0.1901322109907977
// 0.1901322109907977
1. BehaviorSubject으로 Subject을 생성한 뒤 이를 'Subscriber A'로 구독한다. Subject가 값을 emit 하고, Subscriber A가 랜덤 숫자를 로그에 찍는다.
// Subscriber A: 0.24957144215097515
2. Subject가 다음 값을 emit하고, Subscriber A가 해당 값을 또 찍는다.
// Subscriber A: 0.8751123892486292
3. Subscriber B가 Subject를 구독하는데, BehaviorSubject이기 때문에 새로운 구독자는 마지막으로 저장된 값을 자동으로 받게 되고 이를 로그에 찍는다.
// Subscriber B: 0.8751123892486292
4. Subject가 새로운 값을 emit 한다. Subscriber A와 B 모두 새로운 값을 받고 로그에 찍는다.
// Subscriber A: 0.1901322109907977
// Subscriber B: 0.1901322109907977
5. .value 속성을 참고하여 최신 값을 로그에 찍었다. 구독할 필요 없이 최신의 값을 가져올 수 있다.
// 0.1901322109907977
BehaviorSubject를 start value와 함께 생성할 수도 있다.
초기 값을 지니고 있는 Observables을 생성하는 것은 좀 어려울 수 있으나, 아래의 예시처럼 초기 값을 전달해주는 BehaviorSubject으로 쉽게 만들 수 있다.
import * as Rx from "rxjs";
const subject = new Rx.BehaviorSubject(Math.random());
// subscriber 1
subject.subscribe((data) => {
console.log('Subscriber A:', data);
});
// output
// Subscriber A: 0.24957144215097515
/
ReplaySubject
BehaviorSubject와 달리, ReplaySubject은 새로운 구독자들에게 기존의 값들을 보내준다.
ReplaySubject는 observable 실행의 일부분을 기록할 수 있어서 여러 개의 기존 값들을 저장하여 새로운 구독자들에게 '리플레이'해줄 수 있다.
ReplaySubject를 생성할 때 몇 개의 값을 얼마나 저장할 것인지 지정할 수 있다.
(예를 들면 : 새로운 구독이 일어나기 1초 전의 5개의 값을 저장)
import * as Rx from "rxjs";
const subject = new Rx.ReplaySubject(2);
// subscriber 1
subject.subscribe((data) => {
console.log('Subscriber A:', data);
});
subject.next(Math.random())
subject.next(Math.random())
subject.next(Math.random())
// subscriber 2
subject.subscribe((data) => {
console.log('Subscriber B:', data);
});
subject.next(Math.random());
// Subscriber A: 0.3541746356538569
// Subscriber A: 0.12137498878080955
// Subscriber A: 0.531935186034298
// Subscriber B: 0.12137498878080955
// Subscriber B: 0.531935186034298
// Subscriber A: 0.6664809293975393
// Subscriber B: 0.6664809293975393
1. ReplaySubject를 생성하고 2개의 값만 저장할 것이라고 지정한다.
2. Subscriber A로 구독한다.
3. subject를 통해 3개의 새로운 값을 실행하였고, Subscriber A는 3개 모두 로그에 찍는다.
// Subscriber A: 0.3541746356538569
// Subscriber A: 0.12137498878080955
// Subscriber A: 0.531935186034298
4. Subscriber B로 구독은 시작한다. ReplaySubject에 2개의 값을 저장한다고 지정했기 때문에 ReplaySubject은 Subscriber B에게 바로 마지막 2개의 값을 emit 해주고 이는 로그에 찍힌다.
// Subscriber B: 0.12137498878080955
// Subscriber B: 0.531935186034298
5. subject이 다른 값을 또 emit 한다. 이번에는 Subscriber A, B 모두 해당 값을 로그에 찍는다.
// Subscriber A: 0.6664809293975393
// Subscriber B: 0.6664809293975393
ReplaySubject(1)으로 생성하면 처음에 값이 emit 되고 나서 항상 1개의 최신 값을 저장하고 있기 때문에
초기값을 설정할 수 있는 BehaviorSubject와 동일하게 사용할 수 있어 보인다.
하지만 경우에 따라 다르게 기능을 하니 이를 살펴보자.
medium.com/javascript-everyday/behaviorsubject-vs-replaysubject-1-beware-of-edge-cases-b361153d9ccf
( 해당 글 일부분을 번역하였습니다. )
BehaviorSubject vs ReplaySubject(1)
1. 막 생성했을 경우 - BehaviorSubject 승!
만약 BehaviorSubject를 사용한다면, 초기값을 설정해줘야 하는데 이는 결과 스트림에다가 startWith를 사용하는 것과 비슷하다. 반대로, ReplaySubject는 초기값을 전달할 수 없다.
import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
function observerFactory(name: string) {
return {
next: color => console.log(`#${name}`, color)
};
}
const behaviorSubject = new BehaviorSubject('Angular 7');
const replaySubject = new ReplaySubject<string>(1);
const behaviorObserver = observerFactory('behavior');
const replayObserver = observerFactory('replay');
const lateReplayObserver = observerFactory('late replay');
behaviorSubject.subscribe(behaviorObserver);
replaySubject.subscribe(replayObserver);
setTimeout(() => {
behaviorSubject.next('Angular 8');
replaySubject.next('Angular 8');
}, 1000);
setTimeout(() => {
replaySubject.subscribe(lateReplayObserver);
}, 1500);
//console output
//---at moment of subscription---
//#behavior Angular 7
//---after 1000ms---
//#behavior Angular 8
//#replay Angular 8
//--after 1500ms---
//#late replay Angular 8
마지막 값(Angular 8)이 'late observer'에서 리플레이되었다.
그러므로 subject에 첫 번째 값을 넣은 뒤에 BehaviorSubject은 ReplaySubject(1)과 동일하게 실행된다.
2. Complete 되었을 경우 - ReplaySubject(1) 승!
만약 이미 complete 된 subject을 구독한다면 BehaviorSubject은 더이상 마지막 값을 전달받을 수 없다. 하지만 ReplaySubject(1)는 complete되기 전의 마지막 값을 전달한다.
import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
function observerFactory(name: string) {
return {
next: color => console.log(`#${name}`, color)
};
}
const behaviorSubject = new BehaviorSubject('Angular 7');
const replaySubject = new ReplaySubject<string>(1);
const behaviorObserver = observerFactory('behavior');
const replayObserver = observerFactory('replay');
const lateBehaviorObserver = observerFactory('late behavior');
const lateReplayObserver = observerFactory('late replay');
behaviorSubject.subscribe(behaviorObserver);
replaySubject.subscribe(replayObserver);
setTimeout(() => {
behaviorSubject.next('Angular 8');
replaySubject.next('Angular 8');
}, 1000);
setTimeout(() => {
behaviorSubject.complete();
replaySubject.complete();
behaviorSubject.subscribe(lateBehaviorObserver);
replaySubject.subscribe(lateReplayObserver);
}, 1500);
//console output
//---at moment of subscription---
//#behavior Angular 7
//---after 1000ms---
//#behavior Angular 8
//#replay Angular 8
//--after 1500ms---
//#late replay Angular 8
결론
- 구독 중에 subject에 값을 넣지 않아도 초기값을 전달하고 싶다면 BehaviorSubject을 사용
- subject가 complete된 후에도 마지막 값을 observer에게 다시 전달해줘야 한다면 ReplaySubject(1)을 사용
'🔴 FE > RxJS' 카테고리의 다른 글
RxJS 'startWith'를 좀 더 똑똑하게 사용하는 팁 (0) | 2021.01.05 |
---|---|
RxJS - 검색 Input 만들기 : debounceTime and distinctUntilChanged (1) | 2020.07.06 |
RxJS를 써야하는 이유!! (2) | 2020.06.25 |
Observable의 특징 (vs promise, events API, arrays) (0) | 2020.06.08 |