Subject
什么是主题? RxJS Subject 是一种特殊类型的 Observable,它允许将值多播到许多观察者。虽然普通的 Observable 是单播的(每个订阅的 Observer 拥有一个独立的 Observable 执行),但主题是多播的。
一个 Subject 就像一个 Observable,但可以多播给许多观察者。主题就像 EventEmitters:它们维护着许多侦听器的注册表。
每个 Subject 都是一个 Observable。给定一个主题,您可以订阅它,提供一个观察者,它将开始正常接收值。从观察者的角度来看,它无法判断 Observable 的执行是来自一个普通的单播 Observable 还是一个 Subject。
在主题内部,订阅不会调用传递值的新执行。它只是在一个观察者列表中注册给定的观察者,类似于 addListener 通常在其他库和语言中的工作方式。
Every Subject is an Observer。它是一个具有 next(v)、error(e) 和 complete() 方法的对象。要向 Subject 提供一个新值,只需调用 next(theValue),它就会被多播给注册监听 Subject 的观察者。
在下面的例子中,我们有两个观察者附加到一个主题,我们为主题提供了一些值:
import { Subject } from 'rxjs';
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(1);
subject.next(2);
// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
由于一个 Subject 是一个观察者,这也意味着你可以提供一个 Subject 作为订阅任何 Observable 的参数,如下例所示:
import { Subject, from } from 'rxjs';
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
const observable = from([1, 2, 3]);
observable.subscribe(subject); // You can subscribe providing a Subject
// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
通过上述方法,我们基本上只是通过 Subject 将单播 Observable 执行转换为多播。这演示了 Subjects 是如何将任何 Observable 执行共享给多个 Observers 的唯一方法。
主题类型还有一些特化:BehaviorSubject、ReplaySubject 和 AsyncSubject。
多播 Observable
“多播 Observable”通过可能有许多订阅者的 Subject 传递通知,而普通的“单播 Observable”只向单个 Observer 发送通知。
一个多播的 Observable 在幕后使用一个 Subject 来让多个 Observer 看到相同的 Observable 执行。
在幕后,这就是multicast多播操作符的工作方式:观察者订阅底层主题,主题订阅源 Observable。下面的例子类似于前面使用 observable.subscribe(subject) 的例子:
import { from, Subject } from 'rxjs';
import { multicast } from 'rxjs/operators';
const source = from([1, 2, 3]);
const subject = new Subject();
const multicasted = source.pipe(multicast(subject));
// These are, under the hood, `subject.subscribe({...})`:
multicasted.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
multicasted.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
// This is, under the hood, `source.subscribe(subject)`:
multicasted.connect();
multicast返回一个 Observable,它看起来像一个普通的 Observable,但在订阅时就像一个 Subject。多播返回一个 ConnectableObservable,它只是一个带有 connect() 方法的 Observable。
connect() 方法对于确定共享 Observable 执行何时开始很重要。因为 connect() 在后台执行 source.subscribe(subject),connect() 返回一个 Subscription,您可以取消订阅以取消共享的 Observable 执行。
引用计数
手动调用 connect() 并处理订阅通常很麻烦。通常,我们希望第一个观察者到达时自动连接,并在最后一个观察者取消订阅时自动取消共享执行。
请考虑以下示例,其中发生此列表中概述的订阅:
1.第一个观察者订阅多播的 Observable
2.多播的 Observable 已连接
3.下一个值 0 传递给第一个观察者
4.第二个观察者订阅多播的 Observable
5.下一个值 1 传递给第一个观察者
6.下一个值 1 传递给第二个观察者\
7.First Observer 取消订阅多播的 Observable
8.下一个值 2 传递给第二个观察者
9.第二个观察者取消订阅多播的 Observable
10.与多播 Observable 的连接被取消订阅
为了通过显式调用 connect() 来实现这一点,我们编写了以下代码:
import { interval, Subject } from 'rxjs';
import { multicast } from 'rxjs/operators';
const source = interval(500);
const subject = new Subject();
const multicasted = source.pipe(multicast(subject));
let subscription1, subscription2, subscriptionConnect;
subscription1 = multicasted.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
// We should call `connect()` here, because the first
// subscriber to `multicasted` is interested in consuming values
subscriptionConnect = multicasted.connect();
setTimeout(() => {
subscription2 = multicasted.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
}, 600);
setTimeout(() => {
subscription1.unsubscribe();
}, 1200);
// We should unsubscribe the shared Observable execution here,
// because `multicasted` would have no more subscribers after this
setTimeout(() => {
subscription2.unsubscribe();
subscriptionConnect.unsubscribe(); // for the shared Observable execution
}, 2000);
如果我们希望避免显式调用 connect(),我们可以使用 ConnectableObservable 的 refCount() 方法(引用计数),它返回一个 Observable 来跟踪它有多少订阅者。当订阅者数量从 0 增加到 1 时,它会为我们调用 connect(),从而开始共享执行。只有当订阅者数量从 1 减少到 0 时才会完全退订,停止进一步执行
refCount 使多播的 Observable 在第一个订阅者到达时自动开始执行,并在最后一个订阅者离开时停止执行。
下面是一个例子:
import { interval, Subject } from 'rxjs';
import { multicast, refCount } from 'rxjs/operators';
const source = interval(500);
const subject = new Subject();
const refCounted = source.pipe(multicast(subject), refCount());
let subscription1, subscription2;
// This calls `connect()`, because
// it is the first subscriber to `refCounted`
console.log('observerA subscribed');
subscription1 = refCounted.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
setTimeout(() => {
console.log('observerB subscribed');
subscription2 = refCounted.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
}, 600);
setTimeout(() => {
console.log('observerA unsubscribed');
subscription1.unsubscribe();
}, 1200);
// This is when the shared Observable execution will stop, because
// `refCounted` would have no more subscribers after this
setTimeout(() => {
console.log('observerB unsubscribed');
subscription2.unsubscribe();
}, 2000);
// Logs
// observerA subscribed
// observerA: 0
// observerB subscribed
// observerA: 1
// observerB: 1
// observerA unsubscribed
// observerB: 2
// observerB unsubscribed
refCount() 方法只存在于 ConnectableObservable 上,它返回一个 Observable,而不是另一个 ConnectableObservable。
BehaviorSubject 行为主体
Subjects 的变体之一是 BehaviorSubject,它具有“当前值”的概念。它存储发送给其消费者的最新值,每当有新的观察者订阅时,它将立即从 BehaviorSubject 接收“当前值”。
BehaviorSubjects 可用于表示“随时间变化的值”。例如,生日的事件流是一个主题,但一个人的年龄流将是一个行为主题。
在以下示例中,BehaviorSubject 被初始化为第一个 Observer 在订阅时收到的值 0。即使第二个观察者在发送值 2 之后订阅了它,它也会收到值 2。
import { BehaviorSubject } from 'rxjs';
const subject = new BehaviorSubject(0); // 0 is the initial value
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.next(1);
subject.next(2);
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(3);
// Logs
// observerA: 0
// observerA: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
ReplaySubject 重播主题
ReplaySubject 与 BehaviorSubject 的相似之处在于它可以将旧值发送给新订阅者,但它也可以记录 Observable 执行的一部分。
ReplaySubject 从 Observable 执行中记录多个值,并将它们重播给新订阅者。
创建 ReplaySubject 时,您可以指定要重播的值数量:
import { ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(3); // buffer 3 values for new subscribers
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(5);
// Logs:
// observerA: 1
// observerA: 2
// observerA: 3
// observerA: 4
// observerB: 2
// observerB: 3
// observerB: 4
// observerA: 5
// observerB: 5
除了缓冲区大小之外,您还可以以毫秒为单位指定窗口时间,以确定记录的值可以保留多长时间。在以下示例中,我们使用 100 的大缓冲区大小,但窗口时间参数仅为 500 毫秒。
import { ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(100, 500 /* windowTime */);
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
let i = 1;
setInterval(() => subject.next(i++), 200);
setTimeout(() => {
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
}, 1000);
// Logs
// observerA: 1
// observerA: 2
// observerA: 3
// observerA: 4
// observerA: 5
// observerB: 3
// observerB: 4
// observerB: 5
// observerA: 6
// observerB: 6
// ...
AsyncSubject 异步主题
AsyncSubject 是一个变体,其中只有 Observable 执行的最后一个值被发送给它的观察者,并且只有在执行完成时。
import { AsyncSubject } from 'rxjs';
const subject = new AsyncSubject();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(5);
subject.complete();
// Logs:
// observerA: 5
// observerB: 5
AsyncSubject 类似于 last() 运算符,因为它等待完整的通知以传递单个值。
Void subject 空题
有时发出的值与发出值的事实无关。 例如,下面的代码表示一秒钟已经过去。
const subject = new Subject<string>();
setTimeout(() => subject.next('dummy'), 1000);
以这种方式传递一个虚拟值很笨拙,可能会使用户感到困惑。 通过声明无效主题,您表示该值无关紧要。只有事件本身很重要。
const subject = new Subject<void>();
setTimeout(() => subject.next(), 1000);
带有上下文的完整示例如下所示:
import { Subject } from 'rxjs';
const subject = new Subject(); // Shorthand for Subject<void>
subject.subscribe({
next: () => console.log('One second has passed')
});
setTimeout(() => subject.next(), 1000);
共用API-asObservalbe
所有的Subject
系列都有一个共用且常用到的API:asObservalbe
,它的用途是将Subject
当作Observalbe
返回。这样有什么好处呢?由于Observable
并没有next/complete/error
这样的API,因此可以让得到这个Observable
对象的程序专注在数据流的订阅相关的处理就好,不被允许发送新的事件。
class Student {
constructor() {
this._score$ = new Subject();
}
get score() {
return this._score$.asObservable();
}
updateScore(score) {
// 大于 70才允许推送
if (score > 70) {
this._score$.next(score);
}
}
}
const Jonny = new Student();
Jonny.score.subscribe((score) => {
console.log(`当前成绩:${score}`);
});
Jonny.updateScore(70);
Jonny.updateScore(50);
Jonny.updateScore(80); // 当前成绩: 80