当前位置: 首页 > news >正文

RxJS: 简单入门

Introduction to RxJS

1. 前言

1.1 什么是RxJS

RxJSReactiveX编程理念的JavaScript版本。ReactiveX来自微软,它是一种针对异步数据流的编程。简单来说,它将一切数据,包括HTTP请求,DOM事件或者普通数据等包装成流的形式,然后用强大丰富的操作符对流进行处理,使你能以同步编程的方式处理异步数据,并组合不同的操作符来轻松优雅的实现你所需要的功能。

1.2 RxJS可用于生产吗?

ReactiveX由微软于2012年开源,目前各语言库由ReactiveX组织维护。RxJSGitHub上已有8782个star,目前最新版本为5.5.2,并持续开发维护中,其中官方测试用例共计2699个。

谁在使用Rx

1.3 RxJS对项目代码的影响?

RxJS中的流以Observable对象呈现,获取数据需要订阅Observable,形式如下:

const ob = http$.getSomeList(); //getSomeList()返回某个由`Observable`包装后的http请求
ob.subscribe((data) => console.log(data));
//在变量末尾加$表示Observable类型的对象。

以上与Promise类似:

const promise = http.getSomeList(); // 返回由`Promise`包装的http请求
promise.then((data) => console.log(data));

实际上Observable可以认为是加强版的Promise,它们之间是可以通过RxJSAPI互相转换的:

const ob = Observable.fromPromise(somePromise); // Promise转为Observable
const promise = someObservable.toPromise(); // Observable转为Promise

因此可以在Promise方案的项目中安全使用RxJS,并能够随时升级到完整的RxJS方案。

1.4 RxJS会增加多少体积?

RxJS(v5)整个库压缩后约为140KB,由于其模块化可扩展的设计,因此仅需导入所用到的类与操作符即可。导入RxJS常用类与操作符后,打包后的体积约增加30-60KB,具体取决于导入的数量。

不要用 import { Observable } from 'rxjs'这种方式导入,这会导入整个 rxjs库,按需导入的方式如下:
import { Observable } from 'rxjs/Observable' //导入类
import 'rxjs/add/operator/map' // 导入实例操作符
import 'rxjs/add/observable/forkJoin' // 导入类操作符

2. RxJS快速入门

2.1 初级核心概念

  • Observable
  • Observer
  • Operator

Observable被称为可观察序列,简单来说数据就在Observable中流动,你可以使用各种operator对流进行处理,例如:

const ob = Observable.interval(1000);
ob.take(3).map(n => n * 2).filter(n => n > 2);

第一步代码我们通过类方法interval创建了一个Observable序列,ob作为源会每隔1000ms发射一个递增的数据,即0 -> 1 -> 2。第二步我们使用操作符对流进行处理,take(3)表示只取源发射的前3个数据,取完第三个后关闭源的发射;map表示将流中的数据进行映射处理,这里我们将数据翻倍;filter表示过滤掉出符合条件的数据,根据上一步map的结果,只有第二和第三个数据会留下来。

上面我们已经使用同步编程创建好了一个流的处理过程,但此时ob作为源并不会立刻发射数据,如果我们在map中打印n是不会得到任何输出的,因为ob作为Observable序列必须被“订阅”才能够触发上述过程,也就是subscribe(发布/订阅模式)。

const ob = Observable.interval(1000);
ob.take(3).map(n => n * 2).filter(n => n > 0).subscribe(n => console.log(n));

结果:

2 //第2秒
4 //第3秒

上面代码中我们给subscribe传入了一个函数,这其实是一种简写,subscribe完整的函数签名如下:

ob.subscribe({
    next: d => console.log(d),
    error: err => console.error(err),
    complete: () => console.log('end of the stream')
})

直接给subscribe传入一个函数会被当做是next函数。这个完整的包含3个函数的对象被称为observer(观察者),表示的是对序列结果的处理方式。next表示数据正常流动,没有出现异常;error表示流中出错,可能是运行出错,http报错等等;complete表示流结束,不再发射新的数据。在一个流的生命周期中,errorcomplete只会触发其中一个,可以有多个next(表示多次发射数据),直到complete或者error

observer.next可以认为是Promisethen的第一个参数,observer.error对应第二个参数或者Promisecatch

RxJS同样提供了catch操作符,err流入catch后,catch必须返回一个新的Observable。被catch后的错误流将不会进入observererror函数,除非其返回的新observable出错。

Observable.of(1).map(n => n.undefinedMethod()).catch(err => {
    // 此处处理catch之前发生的错误
    return Observable.of(0); // 返回一个新的序列,该序列成为新的流。
});

2.2 创建可观察序列

创建一个序列有很多种方式,我们仅列举常用的几种:

Observable.of(...args)

Observable.of()可以将普通JavaScript数据转为可观察序列,点我测试。

Observable.fromPromise(promise)

Promise转化为Observable,点我测试。

Observable.fromEvent(elment, eventName)

DOM事件创建序列,例如Observable.fromEvent($input, 'click'),点我测试。

Observable.ajax(url | AjaxRequest)

发送http请求,AjaxRequest参考这里

Observable.create(subscribe)

这个属于万能的创建方法,一般用于只提供了回调函数的某些功能或者库,在你用这个方法之前先想想能不能用RxJS上的类方法来创建你所需要的序列,点我测试。

2.3 合并序列

合并序列也属于创建序列的一种,例如有这样的需求:进入某个页面后拿到了一个列表,然后需要对列表每一项发出一个http请求来获取对应的详细信息,这里我们把每个http请求作为一个序列,然后我们希望合并它们。
合并有很多种方式,例如N个请求按顺序串行发出(前一个结束再发下一个);N个请求同时发出并且要求全部到达后合并为数组,触发一次回调;N个请求同时发出,对于每一个到达就触发一次回调。
如果不用RxJS,我们会比较难处理这么多情形,不仅实现麻烦,维护更麻烦,下面是使用RxJS对上述需求的解决方案:

const ob1 = Observable.ajax('api/detail/1');
const ob2 = Observable.ajax('api/detail/2');
...
const obs = [ob1, ob2...];
// 分别创建对应的HTTP请求。
  1. N个请求按顺序串行发出(前一个结束再发下一个)
Observable.concat(...obs).subscribe(detail => console.log('每个请求都触发回调'));
  1. N个请求同时并行发出,对于每一个到达就触发一次回调
Observable.merge(...obs).subscribe(detail => console.log('每个请求都触发回调'));
  1. N个请求同时发出并且要求全部到达后合并为数组,触发一次回调
Observable.forkJoin(...obs).subscribe(detailArray => console.log('触发一次回调'));

3. 使用RxJS实现搜索功能

搜索是前端开发中很常见的功能,一般是监听<input />keyup事件,然后将内容发送到后台,并展示后台返回的数据。

<input id="text"></input>
<script>
    var text = document.querySelector('#text');
    text.addEventListener('keyup', (e) =>{
        var searchText = e.target.value;
        // 发送输入内容到后台
        $.ajax({
            url: `/search/${searchText}`,
            success: data => {
              // 拿到后台返回数据,并展示搜索结果
              render(data);
            }
        });
    });
</script>

上面代码实现我们要的功能,但存在两个较大的问题:

  • 多余的请求

当想搜索“爱迪生”时,输入框可能会存在三种情况,“爱”、“爱迪”、“爱迪生”。而这三种情况将会发起 3 次请求,存在 2 次多余的请求。

  • 已无用的请求仍然执行

一开始搜了“爱迪生”,然后马上改搜索“达尔文”。结果后台返回了“爱迪生”的搜索结果,执行渲染逻辑后结果框展示了“爱迪生”的结果,而不是当前正在搜索的“达尔文”,这是不正确的。

减少多余请求数,可以用 setTimeout 函数节流的方式来处理,核心代码如下:

<input id="text"></input>
<script>
    var text = document.querySelector('#text'),
        timer = null;
    text.addEventListener('keyup', (e) =>{
        // 在 250 毫秒内进行其他输入,则清除上一个定时器
        clearTimeout(timer);
        // 定时器,在 250 毫秒后触发
        timer = setTimeout(() => {
            console.log('发起请求..');
        },250)
    })
</script>

已无用的请求仍然执行 的解决方式,可以在发起请求前声明一个当前搜索的状态变量,后台将搜索的内容及结果一起返回,前端判断返回数据与当前搜索是否一致,一致才走到渲染逻辑。最终代码为:

<input id="text"></input>
<script>
    var text = document.querySelector('#text'),
        timer = null,
        currentSearch = '';

    text.addEventListener('keyup', (e) =>{
        clearTimeout(timer)
        timer = setTimeout(() => {
            // 声明一个当前所搜的状态变量
            currentSearch = '书'; 

            var searchText = e.target.value;
            $.ajax({
                url: `/search/${searchText}`,
                success: data => {
                    // 判断后台返回的标志与我们存的当前搜索变量是否一致
                    if (data.search === currentSearch) {
                        // 渲染展示
                        render(data);
                    } else {
                        // ..
                    }
                }           
            });
        },250)
    })
</script>

上面代码基本满足需求,但代码开始显得乱糟糟。我们来使用RxJS实现上面代码功能,如下:

var text = document.querySelector('#text');
var inputStream = Rx.Observable.fromEvent(text, 'keyup') //为dom元素绑定'keyup'事件
                    .debounceTime(250) // 防抖动
                    .pluck('target', 'value') // 取值
                    .switchMap(url => Http.get(url)) // 将当前输入流替换为http请求
                    .subscribe(data => render(data)); // 接收数据

RxJS能简化你的代码,它将与流有关的内部状态封装在流中,而不需要在流外定义各种变量来以一种上帝视角控制流程。Rx的编程方式使你的业务逻辑流程清晰,易维护,并显著减少出bug的概率。

个人总结的常用操作符:

类操作符(通常为合并序列或从已有数据创建序列)
合并 forkJoin, merge, concat
创建 of, from, fromPromise, fromEvent, ajax, throw
实例操作符(对流中的数据进行处理或者控制流程)
map, filter,switchMap, toPromise, catch, take, takeUntil, timeout, debounceTime, distinctUntilChanged, pluck
对于这些操作符的使用不再详细描述,请参阅网上资料。

中文官网 http://cn.rx.js.org/
附上个人翻译的一些文章

  • RxJS:冷热模式的比较
  • RxJS: map, flatMap和flatMapLatest的区别
  • RxJS: 详解forkJoin, zip, combineLatest之间的区别

参考文章:构建流式应用:RxJS 详解

相关文章:

  • 使用秘钥登录服务器
  • JavaScript学习(1)之JavaScript基础
  • 依赖倒置原则(Dependency Inversion Principle)
  • 物联网下的工控机产业发展迅速 潜力正不断被挖掘
  • 列表与元组的区别
  • Linux下curl命令的使用
  • JAVA 集合框架
  • CentOS6.x安装memcached-1.5.x
  • spring boot整合mybatis+mybatis-plus
  • Android 3.1 r1 API中文文档(6)——ImageView
  • 招聘网工:内推性质
  • 事件之道~一 如何让实体发生更新时,同时记录它更新的内容到日志表
  • 如何在最段的时间内让搜索引擎收录一个新网站?
  • 云上数据安全,初识数据库审计
  • Android 中文API (33) —— Checkable
  • JS实现简单的MVC模式开发小游戏
  • Laravel Mix运行时关于es2015报错解决方案
  • LeetCode算法系列_0891_子序列宽度之和
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Octave 入门
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • PV统计优化设计
  • SpringCloud集成分布式事务LCN (一)
  • 给Prometheus造假数据的方法
  • 规范化安全开发 KOA 手脚架
  • 简单基于spring的redis配置(单机和集群模式)
  • 来,膜拜下android roadmap,强大的执行力
  • 那些年我们用过的显示性能指标
  • 配置 PM2 实现代码自动发布
  • 让你的分享飞起来——极光推出社会化分享组件
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 通过几道题目学习二叉搜索树
  • 为视图添加丝滑的水波纹
  • 译米田引理
  • MyCAT水平分库
  • 移动端高清、多屏适配方案
  • # Java NIO(一)FileChannel
  • #define,static,const,三种常量的区别
  • #QT(一种朴素的计算器实现方法)
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • #图像处理
  • (20050108)又读《平凡的世界》
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (顺序)容器的好伴侣 --- 容器适配器
  • (转)详解PHP处理密码的几种方式
  • (状压dp)uva 10817 Headmaster's Headache
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET连接数据库方式
  • @Autowired多个相同类型bean装配问题
  • @Builder用法
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • @RunWith注解作用
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname