프로그래밍(Basic)/Javascript(TS,Node)

[바미] RxJS - BehaviorSubject

Bami 2022. 3. 10. 11:55
728x90
반응형

안녕하세요. 오늘은 BehaviorSubject에 대해 알아보고자 합니다.

 

먼저 BehaviorSubject는 모든 새 구독에 저장하고 내보내는 현재 값의 개념이 있는 주제의 변형입니다 . 

이 현재 값은 소스 Observable에 의해 가장 최근에 방출된 항목이거나 아직 방출되지 않은 경우 시드/기본값입니다. 

 

항상 현재 값이 BehaviorSubject있어야 하므로 초기화 시 초기 값이 필요합니다. 

구독 시 마지막으로 내보낸 값을 원하지만 시드 값을 제공하지 않으려면 대신 ReplaySubject 를 확인하세요.

 

BehaviorSubject이 동작은 구독자가 값이 저장된 것보다 훨씬 늦게 구독하더라도 항상 마지막으로 내보낸 값을 직접 가져올 수 있음을 의미합니다 . 

관찰자가 a BehaviorSubject를 구독하면 먼저 현재 값을 내보낸 다음 구독 후 소스 Observable에서 내보낸 다른 항목을 계속 내보냅니다. 

 

그러나 ReplaySubject와 달리 BiorSubject가 중지된 상태이면 COMPLETE 또는 ERROR 알림만 내보내는 반면, 중지된 상태에서도 ReplaySubject는 COMPLETE 또는 ERROR 알림을 전송하기 전에 캐시된 값을 재생합니다.

 

Subjects는 일반적으로 구독한 관찰 가능 항목이 완료되거나 오류를 생성할 때 중지됨 상태로 전환됩니다.

소스 관찰 가능 항목에 오류가 있는 경우 BehaviorSubject후속 구독에 항목을 내보내지 않습니다. 

대신 소스 Observable에서 새 구독으로 오류 알림을 전달하기만 하면 됩니다.

BehaviorSubject다음과 같은 방식으로 작동합니다.

  1. 내부 구독 컨테이너 만들기
  2. 주제가 유지하는 현재 값을 인스턴스화하는 동안 인수로 전달된 초기 값으로 설정합니다.
  3. 새로운 구독이 발생하면 컨테이너에 추가하고 현재 값을 해당 관찰자에게 내보냅니다.
  4. 소스 옵저버블이 새 값 next을 내보내거나 주제에서 메서드가 호출되면 현재 값을 방출된 값으로 업데이트하고 모든 구독에 대해 옵저버에게 보냅니다.
  5. 소스 옵저버블이 완료되거나 주제에서 메서드 complete가 호출되면 주제의 상태 stopped와 현재 값을 완전한 알림으로 설정합니다. 모든 기존 구독에 전체 알림을 보내고 컨테이너에서 제거
  6. 소스 옵저버블에서 오류가 발생하거나 주제에서 메서드 error가 호출되면 주제의 상태를 알림으로 설정 stopped하고 현재 값을 error알림으로 설정합니다. error모든 기존 구독에 알림을 보내고 컨테이너에서 제거
  7. 주제가 stopped인 경우 컨테이너에 새 구독을 추가하지 말고 대신 구독 즉시 현재 값(완료 또는 오류 알림)을 해당 관찰자에게 보냅니다.
  8. stopped주제가 관찰 가능한 새 소스에 가입되어 있으면 이 소스의 값을 무시하십시오.

다음 다이어그램은 이 일련의 단계를 보여줍니다.

Usage

BehaviorSubject의 가장 일반적인 사용 사례는 구독자가 필요할 때 최신 값을 읽을 수 있는 저장소 또는 캐시 역할을 하는 것입니다. 다음은 상점의 기본 구현을 보여주는 예입니다.

 

<>Copy
const state1 = {name: 'James', age: 33};
const state2 = {name: 'Anna', age: 27};

const store = new BehaviorSubject(state1);

const v1 = getValueFromStore();
console.log(v1.name); // 'James'

updateStore(state2);

const v2 = getValueFromStore();
console.log(v2.name); // 'Anna'

selectFromStore((state) => state.age).subscribe((v) => console.log(v));

function updateStore(v) {
   store.next(v);
}

function getValueFromStore() {
   return store.value;
}

function selectFromStore(selector) {
   return store.asObservable().pipe(
       map(selector)
   );
}

 

Simple BehaviorSubject예제

// RxJS v6+
import { BehaviorSubject } from 'rxjs';

const subject = new BehaviorSubject(123);

// two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);

// two subscribers will get new value => output: 456, 456
subject.next(456);

// new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);

// all three subscribers will get new value => output: 789, 789, 789
subject.next(789);

// output: 123, 123, 456, 456, 456, 789, 789, 789

마우스 클릭으로 생성된 새 구독자가 있는 BehaviorSubject

// RxJS v6+
import { BehaviorSubject, fromEvent, interval, merge } from 'rxjs';
import { map, tap, mergeMap } from 'rxjs/operators';

const setElementText = (elemId, text) =>
  (document.getElementById(elemId).innerText = text.toString());
const addHtmlElement = coords =>
  (document.body.innerHTML += `
  <div 
    id=${coords.id}
    style="
      position: absolute;
      height: 30px;
      width: 30px;
      text-align: center;
      top: ${coords.y}px;
      left: ${coords.x}px;
      background: silver;
      border-radius: 80%;"
    >
  </div>`);

const subject = new BehaviorSubject(0);

const click$ = fromEvent(document, 'click').pipe(
  map((e: MouseEvent) => ({
    x: e.clientX,
    y: e.clientY,
    id: Math.random()
  })),
  tap(addHtmlElement),
  mergeMap(coords => subject.pipe(tap(v => setElementText(coords.id, v))))
);

const interval$ = interval(1000).pipe(
  tap(v => subject.next(v)),
  tap(v => setElementText('intervalValue', v))
);

merge(click$, interval$).subscribe();

추가적으로 제가 실제로 사용하고 있는 부분이라 더 자세히 알아보기 위해 이 글을 쓰게 되었습니다.

import * as Mydatas from './folder/index'

export class CommonDatas {
    constructor() {
        this.Exam = new Mydatas.MyClass();
    }
    
    get MyClass() { return this.Exam; }
import { BehaviorSubject } from "rxjs";
import { CommonDatas } from "./CommonDatas";

class MyData {
    GetDataSave() {

            return {
                mydatas: {
                    exam: this.CommonDatas.MyClass.GetDataSave(),
                }
     
            };
        }

    LoadData(data) {
        this.CommonDatas.MyClass.LoadData(data.mydatas.appearance);
	}
}
import { BehaviorSubject } from 'rxjs';

export class MyClass {
    constructor() {
        this.bool = new BehaviorSubject(true);
        this.string = new BehaviorSubject("hello");
        this.int = new BehaviorSubject(123);
    }

이렇게 상위 클래스와 constructor()에 먼저 생성한 뒤에 ExamClass의 값을 변경해주어야 할 때가 오면

import CommonDatas from 'CommonDatas';

export default class AnotherClass extends ExtendsClass {
    constructor() {
        this._data = CommonDatas.MyData.Myclass;
        this._Init();
    }
    
    _Init() { 
    	let button = new Buttons();
        let toggle = new Toggle();
        
        button.on('click', (isClick) => {
        	if (isClick)
            	this._data.string.next("world!");
            else
            	this._data.string.next("hello");
       	}
        
         toggle.on('change', (isOn) => {
            this._data.bool.next(isOn);
        });
        
        this._data.string.subscribe(()=> {
        	let count = 0;
            count++;
            this._data.int.next(count);
       	});
    }

이렇게 바꿔주는 식으로도 사용할 수 있는데요.

상위 클래스에 저런식으로 썼던 이유는 공통적으로 사용해야 하는 데이터를 컨트롤 해주기 위해서 입니다.

import CommonDatas from 'CommonDatas';

단순히 이런식으로 import만 해주면 어떤 Class에서든지 사용할 수 있기 때문이죠.

 

지금까지 RxJS의 BehaviorSubject가 무엇인지, 다양한 예제를 가지고 알아보았습니다.

추가적으로 알려주실 부분이나 틀린 부분이 있다면 댓글로 언제든지 알려주세요! 읽어주셔서 감사합니다.

 

참고자료

728x90
반응형