はじめに
前回の記事では、RxJSの基本となる Observable / Observer / Operator について解説しました。
今回はその続編として、Angular + RxJS を使ううえで必ず出てくるSubjectとSubscriptionを取り上げます。
本記事では、次のポイントを中心に解説します。
- ObservableとSubjectの役割と使い分け
- subscribeした購読はどう管理すべきか
- unsubscribeが必要になる理由とタイミング
それぞれをコード例とあわせて確認しながら、使いどころのイメージを掴んでいきましょう。
Subject/Subscription
今回扱うのは、以下の2つです。
- Subject:値を流す側(Observable)にも、受け取る側(Observer)にもなれる存在
- Subscription:ObservableとObserverを結びつける購読情報
Subject
Subjectは値を流す側(Observable)にも、受け取る側(Observer)にもなれる存在です。
つまり、
-
subscribe()されて値を配ることもできる -
next()を呼んで自分から値を流すこともできる
という特徴を持っています。
Observableとの違い
Observableとは、以下の違いがあります。
| Observable | Subject | |
|---|---|---|
| 値の流し方 | 定義された処理に従って流れる | 明示的にnext()で流す |
| 値の送信 | 外部から直接送れない | 外部から送れる |
| 複数購読 | 可能(基本は独立) | 同じ値を全購読者に配信 |
それぞれに違いについてコード例を見ていきましょう
値の流し方の違い
Observable:定義された処理に従って流れる
import { Observable } from 'rxjs';
const number$ = new Observable<number>(subscriber => {
subscriber.next(1);
subscriber.next(2);
});
number$.subscribe(num => {
console.log('番号 受信:', num);
});
番号 受信: 1
番号 受信: 2
Observableでは、「どの値をどの順番で流すか」はObservableを作った時点で定義されています。
受け取る側は、その流れを購読するだけで、値の発生タイミングや内容を外部から変更することができません。
Subject:明示的にnext()で流す
import { Subject } from 'rxjs';
const numberChange$ = new Subject<number>();
numberChange$.subscribe(num => {
console.log('番号 受信:', num);
});
// 任意のタイミング・任意の処理から明示的に値を流せる
numberChange$.next(1);
番号 受信: 1
Subjectでは、next()を呼んだタイミングで値が流れます。
値を流す処理は Subjectの外側にあり、任意のタイミングで明示的に値を流すことができます。
値の送信の違い
Observable:外部から直接送れない
const number$ = new Observable<number>(subscriber => {
subscriber.next(1);
});
number$.next(2); //←できない
Observableでは、値を流せるのはObservableの定義内だけです。
受け取る側と、流す側が明確に分かれています。
Subject:外部から値を送れる
const number$ = new Subject<number>();
number$.next(1);
number$.next(2);
Subjectでは、外部の任意の処理から値を送れます。
複数購読時の違い
Observable:購読ごとに独立した流れ
const number$ = new Observable<number>(subscriber => {
subscriber.next(Math.random());
});
number$.subscribe(num => {
console.log('処理開始');
console.log('購読A:', num);
});
number$.subscribe(num => {
console.log('購読B:', num);
});
処理開始
購読A: 0.33
処理開始
購読B: 0.21
Observableでは、subscribe()ごとに処理が独立して実行されます。
Subject:1つの値を全購読者で共有
const number$ = new Subject<number>();
number$.subscribe(num => {
console.log('購読A:', num);
});
number$.subscribe(num => {
console.log('購読B:', num);
});
number$.next(Math.random());
購読A: 0.92
購読B: 0.92
値は1度だけ生成されていますが、購読A、購読Bの両方に同じ値が届いています。
Subscription
Subscriptionは、subscribe()を呼んだ結果として得られる、
「ObservableとObserver のつながり(購読)」 を表すオブジェクトです。
Subscriptionの基本
Subscriptionは購読を続けるか、解除するかを管理する役割を持ちます。
購読状態の管理が必要な理由
Subjectやイベント・状態監視などのObservableは、明示的に止めない限り動き続けるという特徴があります。
そのため、購読解除をしないと、
- コンポーネントが破棄された後も処理が残る
- 意図しないログ出力や処理実行が続く
などの問題が発生します。
では、購読から購読を解除するまでの流れを見ていきましょう。
Subjectを購読する
import { Subject } from 'rxjs';
const statusChange$ = new Subject<string>();
const subscription = statusChange$.subscribe(status => {
console.log('ステータス変更:', status);
});
ここでは、
-
statusChange$:ステータス変更を通知するSubject -
subscribe():購読を開始 - subscription:その購読状態を表すオブジェクト
が作られています。
値を流す
statusChange$.next('loading');
statusChange$.next('success');
ステータス変更: loading
ステータス変更: success
購読が解除されていない間は、next()が呼ばれるたびに、
購読している処理に値が届きます。
購読を解除する(unsubscribe)
subscription.unsubscribe();
これ以降は、値を受け取りません。
終わりに
SubjectとSubscriptionは、
「値をどう流すか」 と 「購読をどう管理するか」 を意識させてくれる仕組みです。
購読がいつ始まり、いつ終わるのかを意識することで、不要な処理やバグを防げるようになります。
RxJSを使う際は、この2点を常にセットで考えることが大切です。
参考