引言
开发ngx(angular 2+)应用时,基本上到处都会用到rxjs
来处理异步请求,事件调用等等。所以经常会使用Subject
来处理源源不断的数据流,比如input text change,toast notification等等。
这都要依赖于Subject
本身既可以是Observable
也可以是Observer
,也就是说subject
既可以作为一个数据源
,也可以本身成为一组订阅者的代理
。
但当处理更加复杂的业务需求时,仅仅用Subject
可能无法满足要求,这个时候就考虑一下rxjs
提供的其他Subject Class
了, 例如BehaviorSubject
ReplaySubject
AsyncSubject
,接下来我们就来看一下他们跟Subject
有什么区别,各自有什么特点,在什么时候更适合使用。
[文中代码均使用typescript
]
Subject
首先我们来创建一个Rxjs Subject
,数据的类型是number
let subject1: Subject<number> = new Subject<number>(); // (A)
然后我们使用Subject
的next
方法来emit(发射)
1条数据
subject1.next(100); (B)
接下来对subject1
创建两个订阅,在subscription
中直接打印接受到的数据
subject1.subscribe((res: number) => console.info("subjectA ",res)); // (C) subject1.subscribe((res: number) => console.info("subjectB ",res));
接下来我在发射两条数据
subject1.next(200); (D) subject1.next(300);
好了,接下来我们就来看看console里面会打印出什么结果。
也许有的同学会觉得结果是这样的,因为Subject
可以接收源源不断的数据嘛,所以无论发射多少次数据,订阅者都能接收到。
//output subjectA 100 subjectB 100 subjectA 200 subjectB 200 subjectA 300 subjectB 300
这个结果不太对
因为Subject
的订阅者只有在订阅后,才能接收到数据源发射过来的值。
所以在代码块C
中, 订阅者在订阅数据源subject1
之前, 无论代码块B
执行多少次, 订阅者也只能收到代码C之后
发射的数据。
正确的结果应该是:
//output subjectA 200 subjectB 200 subjectA 300 subjectB 300
这种情况在项目里经常能遇到,有时候我明明从数据源发射一个数据,但在订阅者拿到的值却是undefined
或者null
,这就是因为订阅者是在数据源发射之后创建的,自然无法接收到数据了。
假如我们想在订阅者创建之后,无论什么时候都能拿到数据, 这应该怎么办呢? 那就要考虑使用BehavIoUrSubject
了。
BehaviorSubject
我们依旧使用刚才的例子, 创建一个BehaviorSubject
,默认值设为0. BehaviorSubject需要给个默认值
然后发射一条数据100,创建一个订阅者,再发射一条数据200,再创建一个订阅者,最后发射一条数据300。
代码如下:
let subject2: BehaviorSubject<number> = new BehaviorSubject<number>(0); subject2.next(100); subject2.subscribe((res: number) => console.info("behavior-subjectA ",res)); subject2.next(200); subject2.subscribe((res: number) => console.info("behavior-subjectB ",res)); subject2.next(300);
这个时候结果就应该是:
//output behavior-subjectA 100 behavior-subjectA 200 behavior-subjectB 200 behavior-subjectA 300 behavior-subjectB 300
由于BehaviorSubject
是可以存储最后一条数据
或者初始默认值
的, 所以无论订阅者什么时候订阅到数据源subject2
上,都能接收到数据。
所以针对订阅者behavior-subjectA
,他订阅的时候,数据流里最后一条数据是100
,他能立即接收到。 然后依次能接收到最新的数据200
和300
。
针对订阅者behavior-subjectB
,他订阅的时候,数据流里最后一条数据是200
,他能立即接收到。 然后只能能接收到最新的数据300
了。
BehaviorSubject
给予我们的便利就是,无论何时订阅到数据源,始终都能拿到最新的或者初始的数据,但也只能拿到一条数据
,但当我们处理input text change
事件时,需要拿到用户输入的所有字符,也就是数据流的所有数据,BehaviorSubject
就无能为力了,这个时候我们考虑使用ReplaySubject
了。
ReplaySubject
我们依旧使用刚才的例子, 创建一个ReplaySubject
,发射两条数据100和200,创建一个订阅者,再发射一条数据300,再创建一个订阅者,最后发射一条数据400。
代码如下:
let subject3: ReplaySubject<number> = new ReplaySubject<number>(); subject3.next(100); subject3.next(200); subject3.subscribe((res: number) => console.info("replay-subjectA ",res)); subject3.next(300); subject3.subscribe((res: number) => console.info("replay-subjectB ",res)); subject3.next(400);
控制打印的结果将是:
//output replay-subjectA 100 replay-subjectA 200 replay-subjectA 300 replay-subjectB 100 replay-subjectB 200 replay-subjectB 300 replay-subjectA 400 replay-subjectB 400
ReplaySubject
会存储数据流中的所有数据
,无论何时
订阅到subject3
,订阅者都能获取了订阅之前数据流里的所有数据,然后依旧获取到接下来获取的到的新数据。
就像ReplaySubject
类名的中Replay
,一旦订阅到数据源,就会将数据流像放电影一样重新放一遍给你。
订阅者replay-subjectA
订阅到subject3
的时候,数据流里已经有了100和200, 接收并打印出来。
最后打印新数据300和400.
订阅者replay-subjectB
订阅到subject3
的时候,数据流里已经有了100,200,300, 接收并打印出来。最后打印新数据400.
接下来就要说下最后一种Subject了,也就是AsyncSubject
AsyncSubject
AsyncSubject
和BehaviorSubject
`ReplaySubject
`有些类似,但不同的是AsyncSubject
只会存储数据流里的最后一条数据
, 而且只有在数据流complete时才会将值发布出去
。AsyncSubject
主要是用来处理异步操作,当数据源是异步请求或者事件处理时,可能会发射出很多数据,如果我们只希望数据源的异步操作完成的时候,订阅者才能接收到值,这个时候就可以使用AsyncSubject
了。
接下来我们看个例子,
创建一个AsyncSubject
, 然后发射数据,创建订阅者,再发射数据。。。
let subject4: AsyncSubject<number> = new AsyncSubject<number>(); subject4.next(100); subject4.next(100); subject4.subscribe((res: number) => console.info("async-subjectA ",res)); subject4.next(300); subject4.subscribe((res: number) => console.info("async-subjectB ",res)); subject4.next(400); subject4.subscribe((res: number) => console.info("async-subjectC ",res)); subject4.complete(); subject4.subscribe((res: number) => console.info("async-subjectD ",res)); subject4.next(500);
最后的结果就应该是:
//output4 async-subjectA 400 async-subjectB 400 async-subjectC 400 async-subjectD 400
由于subject4
是AsyncSubject
,只有在complete
的时候才会向订阅者publish
数据,而且只publish
最后一次数据,所以无论订阅者何时订阅数据源,都可以接收到最后一次数据。
但为什么没有打印出500
呢,因为数据源已经complete
了,你就无法再发射新数据了。
总结
最后来总结一下四种Subject
的特点,理解好的各自的特点,在项目开发中可以处理很多棘手的需求,同时也会避免很多问题的发生。