개념 비유 및 예시
1. Observable (관찰 가능 객체)
비유: TV 방송
Observable은 TV 방송과 같음. TV 방송에서는 여러 프로그램이 송출되고, 이 방송을 보고 싶어하는 사람들이 있음. 이 방송은 데이터 스트림을 제공함.
→ Observable은 데이터를 계속해서 흘려보내는 역할.
2. Observer (관찰자)
비유: TV 시청자
Observer는 TV를 시청하는 사람과 같음. 시청자는 방송을 보고 그 내용에 따라 반응함.
→ Observer는 Observable로부터 데이터를 받고, 그 데이터를 처리하는 역할을 함.
(방송이 바뀔 때마다 시청자는 그에 맞춰 반응).
3. Subscription (구독)
비유: TV 수신기
Subscription은 TV 수신기와 같음. 수신기는 방송을 수신하기 위해 방송국과 연결되어 있어야 함.
마찬가지로, Observer(관찰자)는 Observable(관찰가능 객체)을 구독하여 그 데이터를 받을 수 있음. 구독이 해제되면 더 이상 방송을 받지 않게 됨.
4. Operators (연산자)
비유: 리모컨
Operators는 TV 리모컨과 같음. 리모컨을 사용하면 방송을 전환하거나 볼륨을 조절하는 것처럼, Operators를 사용하면 Observable(관찰가능 객체)의 데이터를 변형하거나 필터링할 수 있음.
→ 특정 프로그램만 보고 싶다면 리모컨으로 채널을 바꾸는 것처럼
- Observable: TV 방송 (데이터 흐름을 제공)
- Observer: TV 시청자 (데이터를 받고 처리)
- Subscription: TV 수신기 (Observable을 구독하여 데이터 수신)
- Operators: 리모컨 (데이터를 변형하거나 조작)
RxJS Operators (연산자)
RxJS는 Observable의 기초 위에 구축된 연산자(Operators) 덕분에 유용하다고 볼 수 있음.
연산자는 복잡한 비동기 코드를 선언적으로 쉽게 구성할 수 있게 해줌.
연산자란?
연산자는 함수임. RxJS에는 두 가지 유형의 연산자가 있음.
- Pipeable Operators: Observable에 파이프하여 사용할 수 있는 연산자.
Ex -
observableInstance.pipe(operator) or observableInstance.pipe(operatorFactory()) 형식으로 사용할 수 있음. 이 연산자는 기존 Observable 인스턴스를 변경하지 않고 새로운 Observable을 반환함.
→ 대표 예시 :
filter(...), map(...), mergeMap(...) 등- Creation Operators: 새로운 Observable을 생성하는 데 사용되는 독립적인 함수.
Ex -
of(1, 2, 3)은 1, 2, 3을 순차적으로 방출하는 Observable을 생성함.Pipeable Operators
Pipeable Operators는 입력으로 Observable을 받고, 새로운 Observable을 반환하는 독립형 함수.
→ 기존 Observable을 수정하지 않음
import { of, map } from 'rxjs'; of(1, 2, 3) .pipe(map((x) => x * x)) .subscribe((v) => console.log(`value: ${v}`)); // 출력: // value: 1 // value: 4 // value: 9
Piping
Pipeable Operators는 보통 여러 개가 연결되어 사용되므로,
여러 연산자를 사용할 때는
pipe() 메서드를 통해 가독성을 높임.obs.pipe(op1(), op2(), op3());
→ 여러 연산자를 한 줄로 연결해 가독성을 높이고, 각각의 연산자는 순차적으로 실행됨.
op()(obs)스타일은 연산자를 직접 Observable에 적용하는 방식인데, op라는 연산자가 주어진 Observable에 적용되어 결과를 반환함.이 스타일은 여러개의 연산자를 사용할 때, 가독성이 떨어지고, 연산자간 순서가 명확하지 않으므로
obs.pipe(op()) 스타일을 사용하는 것이 좋음. (스타일 가이드)Creation Operators
Creation Operators는 Observable을 생성하는 데 사용됨.
Ex -
interval 함수는 주어진 시간 간격마다 값을 방출하는 Observable을 생성함import { interval } from 'rxjs'; const observable = interval(1000); // 1초마다 값을 방출
고차 Observable (Higher-order Observables)
고차 Observable은 일반적인 값(문자열, 숫자)을 방출하는 Observable과 달리,
다른 Observable을 방출하는 Observable
Ex - 파일의 URL을 방출하는 Observable
const fileObservable = urlObservable.pipe(map((url) => http.get(url)));
위 코드에서
urlObservable은 URL 문자열을 방출하고, http.get(url)은 그 URL로부터 파일을 가져오는 Observable을 생성함. →
fileObservable은 "파일을 가져오는 Observable"을 방출하는 고차 Observable이 됨.Flattening Higher-order Observables
고차 Observable을 다루기 위해서는 일반적인 Observable로 "평탄화(flattening)"해야 함.
→ 고차 Observable을 일반 Observable로 변환하는 과정이 필요함.
concatAll() 연산자로 각 "inner" Observable을 구독하고, 방출된 값을 모두 연결할 수 있음const fileObservable = urlObservable.pipe( map((url) => http.get(url)), concatAll() );
→
concatAll() 연산자는 "외부" Observable(
urlObservable)에서 방출된 각 "내부" Observable(각 http.get(url))에 구독하고, 해당 Observable이 완료될 때까지 방출된 모든 값을 복사한 후, 다음 Observable로 넘어감.
→ 모든 값이 연결되어 출력됨.
다른 flattening 연산자
- mergeAll()
- 각 내부 Observable이 도착할 때마다 구독하고, 그 값이 도착하는 대로 방출.
→ 여러 내부 Observable에서 방출된 값을 동시에 처리
- switchAll()
- 첫 번째 내부 Observable이 도착할 때 구독하고, 그 값이 도착하는 대로 방출
- 그러나 새로운 내부 Observable이 도착하면 이전 Observable의 구독을 해제하고 새로운 Observable에 구독
→ 항상 최신 Observable의 값을 방출
- exhaustAll()
- 첫 번째 내부 Observable이 도착할 때 구독하고, 그 값이 도착하는 대로 방출
- 이때 첫 번째 Observable이 완료될 때까지 새로운 내부 Observable은 무시
→ 현재 진행 중인 Observable이 완료될 때까지 다른 Observable은 대기
연산자의 카테고리
RxJS에는 여러 연산자가 있고, 다음과 같은 카테고리로 나눌 수 있음.
- Creation Operators: Observable 생성
- Transformation Operators: Observable의 값 변환
- Filtering Operators: 특정 조건에 맞는 값만 방출
- Joining Operators: 여러 Observable 결합
- Multicasting Operators: Observable의 값을 여러 구독자에게 전달
- Error Handling Operators: 오류를 처리
- Utility Operators: 다양한 유틸리티 기능 제공
사용자 정의 연산자 만들기
자주 사용하는 연산자 조합이 있다면,
pipe() 함수를 사용하여 새 연산자를 만들 수 있음. Ex - 홀수를 버리고 짝수를 두 배로 만드는 연산자를 만들 수 있음.
import { pipe, filter, map } from 'rxjs'; function discardOddDoubleEven() { return pipe( filter((v) => !(v % 2)), map((v) => v + v) ); }