美文网首页
Rxjs总结

Rxjs总结

作者: Supreme_Monster | 来源:发表于2020-01-10 15:10 被阅读0次

    ## 介绍Rxjs

    ### 我们以现实的场景为例

    #### 场景1:一个页面需要首先加载底图,还有相关用到的图片,此外,还需要从后台读取数据前台渲染一个树形结构,还需要默认选中几个,地图再根据默认选中的图层显示相关要素。我们来梳理一下顺序:

    - (1) 加载底图

    - (2) 加载图片 图层

    - (3) 后台读取数据形成树结构并默认选中

    - (4) 地图根据选中显示要素

     实现这样的功能逻辑并不难,但要处理好几个异步加载的先后顺序关系。用最原始的方法,无非就是嵌套多层的回调函数,好一点的处理办法是promise,但治标不治本,并且无法多次响应异步,再更进一步可以采用async/await的方法去解决,这样看起来就像同步代码,但依旧有问题,await会阻塞代码,即使后面的代码不依赖于前者的完成,但依旧会等待,这样显然性能会有问题。

    **Rxjs的核心思想 Reactive Programming**

    > 在一些前端框架例如Vue,Angular等很突出的一个特性就是双向绑定,其实现的原理可以简单理解为一种观察-订阅者模式,当我们在使用 Vue开发时,只要一有绑定的变数发生改变,相关的变数及画面也会跟着变动,我们不需要写这其中如何通知发生变化的代码,只需要专注在发生变化时要做什么事,这就是典型的 Reactive Programming,Reactive Programming 简单来说就是 当变数或资源发生变动时,由变数或资源自动告诉我发生变动了。

    >

     了解了这个概念,我们再来梳理一下刚才的思路,如下:

    ![image](./graph.png)

     可以很清晰的表达先后顺序关系,尤其在网速慢的情况下,如果不严格控制异步代码的执行先后,极有可能报错。

    ##### Rxjs的解决方案

    - **Observer**

     表示观察者,是一个对象,有三个回调函数,next、error、complete。。

    - **Observable**

     表示被观察者,实现上是一个函数对象,谁订阅了,就执行此函数的函数体。

    - **Subject**

     首先我们来创建一个Subject,  Subject 是一种特殊类型的 Observable,它允许将值多播给多个观察者,当我们的底图加载完成后,该通知对象向外发送通知,类似于EventEmitters。

    ```

      private mapOb: Subject<boolean> = new Subject<boolean>()

    ```

    &emsp;然后我们使用Subject的next方法来emit(发射)1条数据

    ```

      this.mapOb.next(true)

    ```

    - **Subscription**

      &emsp;我们再创建一个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

                })

    }

    ```

    &emsp;我们每次next的参数通过type来区分,Rxjs也提供了和数组非常类似的函数,如filter,map等运算,可以很方便地对各种数据进行一系列操作变换后取出想要的数据,这个例子中就表示订阅了type为KEY_REQUEST_MAP的底图请求,当接受到来自这样的请求后,才会加载底图。加载完成后,通过next方法通知其他组件底图加载完成。

    &emsp;同样的,我们在加载图层的组件和加载树结构的组件中都订阅该事件

    ```

        this.mapLoadedSub = this.eventBus

          .pipe(filter((event: EventData) => event.type === EventDataType.MAP_LOADED))

          .subscribe(d => {

          //执行逻辑

          })

    ```

    &emsp;这样就可以控制住异步请求的先后顺序关系。

    &emsp;事情还没结束,上图中还有一个步骤,那么如何检测图层和树结构都加载完成后再执行后面的逻辑呢?

    &emsp;这时候就需要用到Rxjs最核心的内容,Operators!

    - **Operators**

    &emsp;Operators即为操作符,刚才说的filter和map就是基本的操作符之一,而Rxjs有很多操作符,合理结合运用非常强大,详情可以查看 [https://rxjs-cn.github.io/learn-rxjs-operators/](https://rxjs-cn.github.io/learn-rxjs-operators/),**掌握Rxjs的关键就在于掌握这些强大的操作符**

    &emsp;刚才的场景我们可以很快的联想到promise里面的all方法,但是再加个场景,例如里面任意一个发生变化都要重新执行新的逻辑,又该如何做呢?显然promise有点力不从心了,但是用Rxjs的操作符,我们就很快可以达到这样的效果。见如下代码:

    ```

    this.initSub = combineLatest(

          this.mapLoadOb,

          race(this.treeLoadOb, this.tab_treeLoadOb)

        ).subscribe(data => {

            // 相应业务逻辑

        }

    ```

    &emsp;底图请求通常是不会变化的,但是导航栏会随着切换而请求不同的数据,这时我们就得将两者订阅结合起来,如果其中一个有变化,combineLatest在合并时,如果任意一个流在等待其他流发射数据期间又发射了新数据,就会使用该流最新发射的数据进行合并,之后每当有某个流发射新数据,也不再等待其他流同步发射完数据,而是使用其他流之前的最近一次数据进行合并。听起来很复杂,还得在实际运用中多去比较很相近的操作符之间细微的区别。

    #### 场景2:跨组件间的通信,父子组件通信,兄弟组件通信

    &emsp;我们知道在Angular中提供了组件间通信的方案,@Input,@OutPut,@ViewChild等一些修饰符以及EventBus的解决方案,但是实际使用中,大量的采用这样的方式导致组件内的依赖过高,且各种各样的方式混在一起,让人很难读懂,这时候就可以用Rxjs去做统一管理。

    &emsp;思想和发送消息是一样的,只专注于数据的传递,而不采用像父组件直接调用子组件方法。

    &emsp;举个很简单的例子,一个地图有各种各样的基本要素,例如指北针,放大缩小按钮,居中按钮等等,我们把所有的这些要素拆分为各种子组件,变成地图组件的一部分,这时候,我们想要实现子组件控制父组件,Rxjs通常是这样做的:

    首先创建一个全局的EventBus服务

    &emsp;

    ```

    export class EventBusService {

      public maintenance: Subject<EventData> = new Subject<EventData>()

      constructor() {}

    }

    ```

    &emsp;子组件(放大缩小按钮组件)订阅,接收到最大比例尺的时候需要将按钮禁用

    ```

    this.maxZoomSub = this.baseFilter

          .filterEventType(this.eventBus, EventDataType.MAX_ZOOM)

          .subscribe(d => {

            this.zoomInEnable = false

          })

    ```

    &emsp;父组件,根据业务逻辑,在最大比例尺的情况下发送消息通知子组件变更

    ```

    if (this.map.getZoom() >= this.map.getMaxZoom()) {

            this.eventBus.maintenance.next({

              type: EventDataType.MAX_ZOOM

            })

          }

    ```

    &emsp;很容易理解,无论是父子组件还是兄弟组件间都可以采取此方式通信。

    ### Tips

    &emsp;Subscription除了订阅(subsrcibe)之外,还有取消订阅(unsubsrcibe),**我们在使用了订阅后一定要及时取消订阅,通常放在组件的销毁生命周期内,否则会造成内存泄漏等隐藏的BUG!!!**

    相关文章

      网友评论

          本文标题:Rxjs总结

          本文链接:https://www.haomeiwen.com/subject/euapactx.html