为什么使用Subject
Subject和observable一样可以被订阅,但是它们在被多次订阅的时候表现是不一样的。
observable在被多次订阅时,每一次的订阅都是独立的,也是重头开始的订阅,订阅之间不会相互干涉。如下所示
const test = interval(1000);
test.subscribe(res => console.log(res))
setTimeout(() => {
test.subscribe(res => console.log(res))
}, 1000);
// 0
// 1
// 0
// 2
// 1
// 3
// 2
....
复制代码
但是在实际情况中,有的时候我们不希望他们是独立的,在很多需要共享触发的情况下,我们会希望他们在订阅的时候,不会从头返回,而是所有订阅都返回相同的触发,而这个就是Subject的作用。
手写一个Subject
理解一个东西最好的办法就是手写一个,Subject的实现可以理解为用一个中介来获取所有observers,然后在触发时同时执行。
const subject = {
observers: [],
subscribe: function(observer) {
this.observers.push(observer)
},
next: function(value) {
this.observers.forEach(o => o.next(value))
},
error: function(error){
this.observers.forEach(o => o.error(error))
},
complete: function() {
this.observers.forEach(o => o.complete())
}
}
const c = interval(1000);
test.subscribe(subject.subscribe(res => cosnole.log('a:' + res)))
setTimeout(() => {
test.subscribe(subject.subscribe(res => cosnole.log('b:' + res)))
}, 1000);
// a:0
// a:1
// b:1
// a:2
// b:2
// a:3
// b:3
// a:4
...
复制代码
Subject的应用
手写的这个Subject和实际的实现有一些小差别,不过将Subject的关键点都覆盖到了。 Subject具有两个特点:
- Subject可以被订阅是一个observable,同时它包含了各种observer,所以它同时是observable和observer
- Subject会对内部包含的所有observer同时触发
依据这两个特性,Subject经常被用在一些需要主动触发的场合,还有一些不同组件之间共享变化的情况。
例如一个网页有一个国际化需求,可以设置多种语言类型,在选择了语言类型之后,所有模块的语言都会发生变化,如果项目很大组件繁多,用简单的组件间通讯就会非常麻烦,熟悉vue的同学一般会用vuex来解决这种问题,而Rxjs就可以很简单的做到。在我们只需要公共的服务中new一个Subject对象,在设置语言变化的时候触发Subject.next(language),然后在需要的做出改变的模块中监听这个Subject的变化输出即可。以angular代码为例:
// common service
languageChange$ = new Subject();
changeLanguage(language){
setLang(language);
this.languageChange$.next(language);
}
复制代码
// other components
constructor(
private _srv: CommonService
) {
this._srv.languageChange$.subscribe(language => {
this.setLanguage(language)
})
}
setLanguage() {
...
}
复制代码
这种需要实现一处变化多处相应的情况,Subject是一把利器。
有一个常用到的需求,在一个组件,常常会有多个observable存在监听各种事件的发生,在关闭这个组件时,我们需要停止掉所有的observable,这个也可以用Subject来统一监听,统一关闭。以angular代码为例:
private _unsubscribe$ = new Subject<void>();
someObservable.pipe(
takeUntil(this._unsubscribe$),
someOperators...
).subscribe(callback)
ngOnDestroy() {
this._unsubscribe$.next();
this._unsubscribe$.complete();
}
复制代码
github上有一个项目封装了一个流程管理的库,用的就是rxjs里的subject。
有兴趣的可以去学习这个库的源码实现,相信对RxJs的理解提高有很大帮助.