概述
本文将会先对RxJS进行阐述解释,然后会向大家逐一介绍一下以下的四个场景和使用到的语法:
- Remote API: Angular 框架中使用
async pipe
将视图流直接映射为视图,以及tap
和map
的使用; - 同一可观察对象的多个订阅的解决方案 -
share()
运算符; - UserEvent: 使用
debounceTime()
和switchMap()
运算符实现动态搜索;以及Timer:switchMap()
结合interval()
做接口轮询; - 使用
takeUntil()
避免内存泄漏; - 使用
combineLatest()
组合技进行前端排序和过滤。
希望通过这四个简短的案例场景的解读,帮助大家对于RxJS有更好的体验感,并在日常的开发场景当中思考并寻找到属于自己的应用场景。
RxJS是什么
RxJS(JavaScript 响应式扩展)是一个使用可观察量进行响应式编程的库,可以更轻松地编写异步或基于回调的代码。
RxJS 提供了 Observable
类型的实现。该库还提供了用于创建和使用可观察量的实用函数。这些实用函数可用于:
- 将异步操作的现有代码转换为可观察量
- 迭代流中的值
- 将值映射到不同类型
- 过滤流
- 组合多个流
说人话,就是:让网页视图能够正确的响应相关的事件。
- 描述数据流
- 组合与转换数据流
- 消费数据流进行视图的更新
场景示例
原料准备:
1.在线运行环境:stackblitz.com/
2.在线mock数据请求:dummyjson.com/docs/produc…
场景1:Angular 框架中使用async pipe
将视图流直接映射为视图
在线示例:RxJS-场景1:async pipe
在上面的例子中,变量products$
从http请求(即getDataList()
)中获取数据流。此时,一旦数据处于可观察(observable)
的形式,除了基础的数据展示,我们还可以继续进行流操作/合并/链接等。在这个例子中,我们只做一次指定数据的过滤和展示。
这里多说一句,在 RxJS 中,我们需要理解一下生产者(Producer)
和消费者(Consumer)
的概念。这个场景例子中, Angular 模板(template
)是一个消费者 - 它订阅(在可观察变量上调用 .subscribe()
方法,或者用.pipe
)products$
,并且每次执行此操作时,它都会创建一个生产者(https 请求)以将数据获取到可观察流中。
在Angular中,可以直接使用内置管道async pipe
(在 html 模板中,片段| async
),它负责将可观察值中的值解析为普通数据,直接将视图流映射为视图。
如果希望制作的更加考究一些,可以使用*ngIf
配合else
制作loading加载效果。虽然在实际项目开发中,我们所用的组件库一般都带有loading的配置项。
在线示例:RxJS-场景1:async pipe + else Template
另外,展开说一下文件里用到的tap
和map
。
tap
常常会和console.log
结合使用,能够帮助我们监控.pipe
管道中方法和方法之间的数据表现是什么样的。如下图所示:我们可以在控制台看到接口返回的数据格式。
在这个例子中,我们只需要将products
字段渲染在视图上,所以加上了map((data) => data.products)
,表示只需要返回其中products
字段。通常,当我们从后端获取的数据不能直接显示的时候,就可以使用map
来执行数据的过滤。
场景2:同一可观察对象的多个订阅的解决方案 - share() 运算符
在线示例:RxJS-场景2:share()
当我们建立多个请求相同内容的订阅者,或者模板中多次使用相同的可观察变量时,可以用share()
“共享”流。
第一种,建立多个请求相同内容的订阅者:制作一个变量productsWithApple$
渲染在页面上。
第二种,在模板中多次使用相同的可观察变量:在模板上多次渲染products$
。
场景3:使用debounceTime()
和switchMap()
运算符实现动态搜索
搜索功能是几乎每个网络应用程序中极其常见的功能。当输入框里输入/网络生成太多请求时,switchMap()
运算符会取消请求。通过取消请求,来节省浏览器/客户端的 CPU 功率,还可以节省实际的服务器资源。
同时,输入框里是连续输入事件,如果每次都响应会触发太多的后端请求,我们希望短时间内多次输入只触发最后一次,可以结合debounceTime()
运算符来实现。
场景3:RxJS-场景3:switchMap() 和 debounceTime()
另外,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
一定要放在管道的最后,来提示之前所有的执行逻辑做一次销毁。否则,订阅无法被正常清除。
更多细节的讲解,可以看下这篇文章:《使用 takeUntil 操作符管理 Angular 组件的订阅》
场景5:使用combineLatest()
组合技进行前端排序和过滤
这个场景来自部长大人的加餐:有的时候可能因为网络原因或者数据量反复请求会导致消耗频繁,是否可以用RxJS,在已有数据上进行操作监听,进行前端过滤/排序功能的制作。
在线示例:RxJS-场景5:combinLatest多管道处理
实现思路就是:
- 初始化从后端请求数据
- 订阅排序
- 订阅数据框
- 使用
combineLatest()
将三者组在一起,使用map()
对数据进行处理/过滤后,进行返回。
资料
RxJs - Stream Analogs in Real Life (2021)
RxJS Quick Introduction (with Examples) for Beginner-Level Angular Developers