RxJS在Angular的使用场景实例

1,351 阅读6分钟

概述

本文将会先对RxJS进行阐述解释,然后会向大家逐一介绍一下以下的四个场景和使用到的语法:

  1. Remote API: Angular 框架中使用async pipe将视图流直接映射为视图,以及tapmap的使用;
  2. 同一可观察对象的多个订阅的解决方案 -share()运算符;
  3. UserEvent: 使用debounceTime()switchMap()运算符实现动态搜索;以及Timer: switchMap()结合interval()做接口轮询;
  4. 使用takeUntil()避免内存泄漏;
  5. 使用combineLatest()组合技进行前端排序和过滤。

希望通过这四个简短的案例场景的解读,帮助大家对于RxJS有更好的体验感,并在日常的开发场景当中思考并寻找到属于自己的应用场景。


RxJS是什么

RxJS(JavaScript 响应式扩展)是一个使用可观察量进行响应式编程的库,可以更轻松地编写异步或基于回调的代码。

RxJS 提供了 Observable 类型的实现。该库还提供了用于创建和使用可观察量的实用函数。这些实用函数可用于:

  • 将异步操作的现有代码转换为可观察量
  • 迭代流中的值
  • 将值映射到不同类型
  • 过滤流
  • 组合多个流

说人话,就是:让网页视图能够正确的响应相关的事件。

  1. 描述数据流
  2. 组合与转换数据流
  3. 消费数据流进行视图的更新

场景示例

原料准备:

1.在线运行环境:stackblitz.com/

2.在线mock数据请求:dummyjson.com/docs/produc…


场景1:Angular 框架中使用async pipe将视图流直接映射为视图

在线示例:RxJS-场景1:async pipe

html文件使用async pipe

ts文件定义Obervable变量

在上面的例子中,变量products$从http请求(即getDataList())中获取数据流。此时,一旦数据处于可观察(observable)的形式,除了基础的数据展示,我们还可以继续进行流操作/合并/链接等。在这个例子中,我们只做一次指定数据的过滤和展示。

这里多说一句,在 RxJS 中,我们需要理解一下生产者(Producer)消费者(Consumer)的概念。这个场景例子中, Angular 模板(template)是一个消费者 - 它订阅(在可观察变量上调用 .subscribe() 方法,或者用.pipeproducts$,并且每次执行此操作时,它都会创建一个生产者(https 请求)以将数据获取到可观察流中。

在Angular中,可以直接使用内置管道async pipe(在 html 模板中,片段| async),它负责将可观察值中的值解析为普通数据,直接将视图流映射为视图。

如果希望制作的更加考究一些,可以使用*ngIf配合else制作loading加载效果。虽然在实际项目开发中,我们所用的组件库一般都带有loading的配置项。

在线示例:RxJS-场景1:async pipe + else Template

loading.gif

另外,展开说一下文件里用到的tapmap

tap常常会和console.log结合使用,能够帮助我们监控.pipe管道中方法和方法之间的数据表现是什么样的。如下图所示:我们可以在控制台看到接口返回的数据格式。

image.png

在这个例子中,我们只需要将products字段渲染在视图上,所以加上了map((data) => data.products),表示只需要返回其中products字段。通常,当我们从后端获取的数据不能直接显示的时候,就可以使用map来执行数据的过滤。


场景2:同一可观察对象的多个订阅的解决方案 - share() 运算符

在线示例:RxJS-场景2:share()

当我们建立多个请求相同内容的订阅者,或者模板中多次使用相同的可观察变量时,可以用share()“共享”流。

第一种,建立多个请求相同内容的订阅者:制作一个变量productsWithApple$渲染在页面上。

加上share()前

加上share()后

第二种,在模板中多次使用相同的可观察变量:在模板上多次渲染products$

加上share()前

加上share()后


场景3:使用debounceTime()switchMap()运算符实现动态搜索

搜索功能是几乎每个网络应用程序中极其常见的功能。当输入框里输入/网络生成太多请求时,switchMap()运算符会取消请求。通过取消请求,来节省浏览器/客户端的 CPU 功率,还可以节省实际的服务器资源。

同时,输入框里是连续输入事件,如果每次都响应会触发太多的后端请求,我们希望短时间内多次输入只触发最后一次,可以结合debounceTime()运算符来实现。

场景3:RxJS-场景3:switchMap() 和 debounceTime()

image.png

另外,switchMap()interval()结合,也可以更好的撰写轮询请求。(关于为什么直接用RxJS的interval()而不是setInterval可以看这篇文档:《Rxjs的interval和timer与Js的setInterval和setTimeout》)

主要的代码撰写思路就是:当下一次轮询执行请求的时候,如果上一个请求还在pending,就取消上一个请求。

component.ts

// 定时刷新告警数据
violationListInterval$: Observable<number> = interval(3000);

constructor() {
    this.violationListInterval$.pipe(switchMap(() => this.api.getData())).subscribe((val) => {console.log(val)})
}

场景4:使用takeUntil()避免内存泄漏

通常,我们手动订阅subscribe,但忘记取消订阅。当然,在Angular 内置的async pipe中(即场景1中的例子)会为我们自动取消订阅。

有两种办法来取消订阅。第一种,我们可以将subscribe()的结果推入一个数组,然后在ngOnDestroy()中遍历数据执行unsubscribe();第二种办法,就是使用takeUntil(),在ngOnDestroy()中关闭所有订阅。

这里着重说一下takeUntil的使用。

RxJS 中,可以使用 takeUntil来控制另外一个 Observable 对象数据的产生。使用 takeUntil,上游的数据直接转手给下游,直到takeUntil的参数吐出一个数据或者完结。

就像一个水龙头开关,一开始是打开的状态,上游的数据一开始直接流到下游,只要 takeUntil 一旦触发吐出数据,水龙头立刻关闭。

利用这点,可以在订阅时,在管道中添加 takeUntil,在组件销毁时吐出数据,这样这些订阅就会立刻关闭,也就达到回收内存的作用。

注意,takeUntil 一定要放在管道的最后,来提示之前所有的执行逻辑做一次销毁。否则,订阅无法被正常清除。

场景3案例里,takeUntil避免内存泄漏

更多细节的讲解,可以看下这篇文章:《使用 takeUntil 操作符管理 Angular 组件的订阅》


场景5:使用combineLatest()组合技进行前端排序和过滤

这个场景来自部长大人的加餐:有的时候可能因为网络原因或者数据量反复请求会导致消耗频繁,是否可以用RxJS,在已有数据上进行操作监听,进行前端过滤/排序功能的制作。

在线示例:RxJS-场景5:combinLatest多管道处理

实现思路就是:

  1. 初始化从后端请求数据
  2. 订阅排序
  3. 订阅数据框
  4. 使用combineLatest()将三者组在一起,使用map()对数据进行处理/过滤后,进行返回。

资料

RxJs - Stream Analogs in Real Life (2021)

响应式编程与流式数据, 从 RxJS 到 Flink

Observable 防腐层项目实战

RxJS Quick Introduction (with Examples) for Beginner-Level Angular Developers

RxJS and Angular: Why and how to use