介绍Rxjs
我们以现实的场景为例
场景1:一个页面需要首先加载底图,还有相关用到的图片,此外,还需要从后台读取数据前台渲染一个树形结构,还需要默认选中几个,地图再根据默认选中的图层显示相关要素。我们来梳理一下顺序:
- (1) 加载底图
- (2) 加载图片 图层
- (3) 后台读取数据形成树结构并默认选中
- (4) 地图根据选中显示要素
实现这样的功能逻辑并不难,但要处理好几个异步加载的先后顺序关系。用最原始的方法,无非就是嵌套多层的回调函数,好一点的处理办法是promise,但治标不治本,并且无法多次响应异步,再更进一步可以采用async/await的方法去解决,这样看起来就像同步代码,但依旧有问题,await会阻塞代码,即使后面的代码不依赖于前者的完成,但依旧会等待,这样显然性能会有问题。
Rxjs的核心思想 Reactive Programming**
在一些前端框架例如Vue,Angular等很突出的一个特性就是双向绑定,其实现的原理可以简单理解为一种观察-订阅者模式,当我们在使用 Vue开发时,只要一有绑定的变数发生改变,相关的变数及画面也会跟着变动,我们不需要写这其中如何通知发生变化的代码,只需要专注在发生变化时要做什么事,这就是典型的 Reactive Programming,Reactive Programming 简单来说就是 当变数或资源发生变动时,由变数或资源自动告诉我发生变动了。
了解了这个概念,我们再来梳理一下刚才的思路,如下:
可以很清晰的表达先后顺序关系,尤其在网速慢的情况下,如果不严格控制异步代码的执行先后,极有可能报错。
Rxjs的解决方案
- **Observer**
 表示观察者,是一个对象,有三个回调函数,next、error、complete。。
- **Observable**
 表示被观察者,实现上是一个函数对象,谁订阅了,就执行此函数的函数体。
- **Subject**
 首先我们来创建一个Subject, Subject 是一种特殊类型的 Observable,它允许将值多播给多个观察者,当我们的底图加载完成后,该通知对象向外发送通知,类似于EventEmitters。
```
private mapOb: Subject<boolean> = new Subject<boolean>()
```
 然后我们使用Subject的next方法来emit(发射)1条数据
```
this.mapOb.next(true)
```
- **Subscription**
 我们再创建一个Subscription,可以理解为对Observable的执行的一个观察者,作为订阅加载底图的请求返回对象,收到请求后再加载底图,完成后再进行刚才的emit操作,用于通知底图已经加载完成,这里的eventBus表示事件总线,会在后面介绍。
```
private mapSub: Subscription
this.mapSub = this.eventBus
.pipe(filter((event: EventData) => event.type === EventDataType.KEY_REQUEST_MAP))
.subscribe(d => {
this.loadMap()
})
...
loadMap(){
...// 加载底图的逻辑
this.eventBus.maintenance.next({
type: EventDataType.MAP_LOADED,
data: true
})
}
```
 我们每次next的参数通过type来区分,Rxjs也提供了和数组非常类似的函数,如filter,map等运算,可以很方便地对各种数据进行一系列操作变换后取出想要的数据,这个例子中就表示订阅了type为KEY_REQUEST_MAP的底图请求,当接受到来自这样的请求后,才会加载底图。加载完成后,通过next方法通知其他组件底图加载完成。
 同样的,我们在加载图层的组件和加载树结构的组件中都订阅该事件
```
this.mapLoadedSub = this.eventBus
.pipe(filter((event: EventData) => event.type === EventDataType.MAP_LOADED))
.subscribe(d => {
//执行逻辑
})
```
 这样就可以控制住异步请求的先后顺序关系。
 事情还没结束,上图中还有一个步骤,那么如何检测图层和树结构都加载完成后再执行后面的逻辑呢?
 这时候就需要用到Rxjs最核心的内容,Operators!
- **Operators**
 Operators即为操作符,刚才说的filter和map就是基本的操作符之一,而Rxjs有很多操作符,合理结合运用非常强大,详情可以查看 [https://rxjs-cn.github.io/learn-rxjs-operators/](https://rxjs-cn.github.io/learn-rxjs-operators/),**掌握Rxjs的关键就在于掌握这些强大的操作符**
 刚才的场景我们可以很快的联想到promise里面的all方法,但是再加个场景,例如里面任意一个发生变化都要重新执行新的逻辑,又该如何做呢?显然promise有点力不从心了,但是用Rxjs的操作符,我们就很快可以达到这样的效果。见如下代码:
```
this.initSub = combineLatest(
this.mapLoadOb,
race(this.treeLoadOb, this.tab_treeLoadOb)
).subscribe(data => {
// 相应业务逻辑
}
```
 底图请求通常是不会变化的,但是导航栏会随着切换而请求不同的数据,这时我们就得将两者订阅结合起来,如果其中一个有变化,combineLatest在合并时,如果任意一个流在等待其他流发射数据期间又发射了新数据,就会使用该流最新发射的数据进行合并,之后每当有某个流发射新数据,也不再等待其他流同步发射完数据,而是使用其他流之前的最近一次数据进行合并。听起来很复杂,还得在实际运用中多去比较很相近的操作符之间细微的区别。
#### 场景2:跨组件间的通信,父子组件通信,兄弟组件通信
 我们知道在Angular中提供了组件间通信的方案,@Input,@OutPut,@ViewChild等一些修饰符以及EventBus的解决方案,但是实际使用中,大量的采用这样的方式导致组件内的依赖过高,且各种各样的方式混在一起,让人很难读懂,这时候就可以用Rxjs去做统一管理。
 思想和发送消息是一样的,只专注于数据的传递,而不采用像父组件直接调用子组件方法。
 举个很简单的例子,一个地图有各种各样的基本要素,例如指北针,放大缩小按钮,居中按钮等等,我们把所有的这些要素拆分为各种子组件,变成地图组件的一部分,这时候,我们想要实现子组件控制父组件,Rxjs通常是这样做的:
首先创建一个全局的EventBus服务
 
```
export class EventBusService {
public maintenance: Subject<EventData> = new Subject<EventData>()
constructor() {}
}
```
 子组件(放大缩小按钮组件)订阅,接收到最大比例尺的时候需要将按钮禁用
```
this.maxZoomSub = this.baseFilter
.filterEventType(this.eventBus, EventDataType.MAX_ZOOM)
.subscribe(d => {
this.zoomInEnable = false
})
```
 父组件,根据业务逻辑,在最大比例尺的情况下发送消息通知子组件变更
```
if (this.map.getZoom() >= this.map.getMaxZoom()) {
this.eventBus.maintenance.next({
type: EventDataType.MAX_ZOOM
})
}
```
 很容易理解,无论是父子组件还是兄弟组件间都可以采取此方式通信。
### Tips
 Subscription除了订阅(subsrcibe)之外,还有取消订阅(unsubsrcibe),**我们在使用了订阅后一定要及时取消订阅,通常放在组件的销毁生命周期内,否则会造成内存泄漏等隐藏的BUG!!!**