Subject、Observable和Observer
Observable的pipe那里说到了供水,但那边只是被动的供水——如果我想要中间往里面加水并让所有吃水者都喝上新的水呢?比如从地下水换成山泉水,带有甘甜的滋味(嗯…)。这里就得用Subject。不过这个地方我得稍微分出来说个东西,那就是Observable和Observer,这俩在用的时候都会有一个next出来,很容易搞混。next即操作水塔,同时也代表去水塔接水,也就是发布方定义传出数据和订阅放接受数据。
// 指定範型 <number>,表示這個流會發送數字
const observable = new Observable((subscriber: Subscriber<number>) => {
subscriber.next(1); // 這裡的 next 是 Subscriber 實作的方法
subscriber.complete();
});
// 訂閱測試
observable.subscribe({
next: (val) => console.log(val),
complete: () => console.log('Done')
});
Subscriber<T>继承了Observer<T>这个接口,而后者约定了必须要实现next、error和complete三个方法:
interface Observer<T> {
next: (value: T) => void;
error: (err: any) => void;
complete: () => void;
}
而next的传入元素在定义observable的时候通过subscriber.next(1)直接给定了,这也是为什么下游直接next: (val) => console.log(val)能取到值的原因,它取到的正是这里的内容。这里搞明白了,我们就知道为啥Subject不同,因为它自身即是一个Observable,同时也是一个Observer。
// 1. 建一個水塔,默認是地下水
const mySubject = new Subject<string>();
// 2. A 跑來接水
mySubject.subscribe(data => console.log('A 收到:', data));
// 3. 把山泉水接入進水塔!
mySubject.next('山泉水');
// 主控台印出: A 收到: 山泉水
这就意味著,此对象的操作不再只是单纯的取水,还可以决定往里面加什么水。只要加了,后续所有的消费者,都会受到影响。所以实务上一般都不太会把Subject直接暴露出来,否则不可控情况下,下游会把整个数据流改的面目全非。
不过Subject具备一个「多播」 (Multicast)的特性,会导致其发布的时候回调全部下游订阅者(执行了subscribe方法的对象)的next,并把mySubject.next('山泉水')这个数据塞过去。也就是说,如果存在此时还未被触发subscribe的对象,它不仅拿不到这个值,其他值也拿不到。就跟一个高压广播水管一样,来了就全部强行喷出,之后就关闸,一滴水也不给你。这会导致的显著问题就可以见以下场景:事件触发机制下的操作,使用者点击之后才会去subscribe。那此时晚来者就会两手空空,什么都拿不到。
BehaviorSubject这时候就派上用场,它是一个持续作用的Subject,不仅会在第一轮塞给所有人山泉水,还会在后面有人来的时候也给他一份,可以说是一个「热情的给水人」,不管什么事来,你要了他就给,跟Subject那样过期不候的高冷性格相对。实际使用的时候,大部分也都是用这个。
但问题来了,若有人就是嘴欠,嫌弃山泉水不行,非要喝地下水怎么办?当前的供水单一,只会让下游取到山泉水——ReplaySubject就派上用场了。如果说前两者,一个是无状态的:Subject晚来了两手空空;一个是只有最新状态:BehaviorSubject无论什么时候来都给你最新的(山泉水);ReplaySubject则是包含了过去和现在指定范围内的全部状态。它可以让下游消费者取到过去的N个资料,即让后续的取水者无论山泉水还是地下水(取决调用时指定的范围),都能拿到手。如果你真的就喜欢喝地下水,自己把山泉水丢弃即可,或者你都想要,全部喝了也无所谓。总之就是将处置权交给下游,水塔只管供水,不管你怎么处置。