Rxjs总结

 介绍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>()

```

&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!!!**

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345