由于上家公司基本每个项目里都会使用到rxjs,趁现在年底了比较空就学习一下rxjs,顺便写写文章做个记录
什么是rxjs
rxjs是一个JavaScript的响应式扩展,全名为Reactive Extensions for JavaScript,响应式扩展不仅限于js一门语言,你在其他地方可能也会听到rxjava,rxrust,rxphp等各自语言对应的响应式扩展,本篇文章只讲述rxjs
说到响应式可能很多人都会想到vue的响应式原理,这边其实就用到了一个设计模式就是观察者模式,通过订阅发布的方式来实现响应式,并且rxjs不光只是观察者模式,他是观察者模式和迭代器模式的结合,所以他很强大
纯函数
我个人把rxjs理解为优雅的函数式编程,当然可能实际也是如此,想要rxjs就要先理解纯函数的概念,纯函数在之前的redux这边文章中有说到,这边给出个官方的说法所谓纯函数就必须满足两个条件
1.函数的执行过程完全由输入参数来决定,不会受除了参数之外的任何数据的影响
2.函数不会修改外部状态
其实像我们平时用过很多纯函数,比如数组的map、filter等方法都是纯函数
第一个例子
首先我们可以执行npm install rxjs来安装一下rxjs这个库,下面给出一个例子来让大家感受下rxjs
import {of} from 'rxjs'
// import {repeat} from 'rxjs'
// 同步数据流
const source$=of(1,2,3).subscribe(console.log)
我们如果执行了这段代码就会看到我们的控制台按顺序输出了1,2,3
这边其实你不需要知道of是什么你只需要知道of产生了一个数据流,这个数据流就是观察者模式中的被观察对象(Observable),那上面这demo中哪个是我们的观察者呢,其实就是console.log,console.log接收of产生的数据流输出出来,与很多地方的概念不同,在rxjs中的subscribe概念是推送的意思,如果我们用数据流图的方式来看就是下图所示
Observable
在rxjs中Observable是一个构造函数,它是创建响应式数据的核心,像上面的of,还是其他的一些Observable创建方法,其实本质都是调用了这个构造函数,来看一个官网的例子
import { Observable } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
console.log('just before subscribe');
observable.subscribe({
next(x) { console.log('got value ' + x); },
error(err) { console.error('something wrong occurred: ' + err); },
complete() { console.log('done'); }
});
console.log('just after subscribe');
可以看到Observable接受一个函数,函数的第一个参数就是subscriber(观察者),观察者有next方法,error方法,complete方法,可以看到但我们执行subscribe的时候我们传了一个对象里面就有这个三个方法,然后我们就可以看到其实它就是执行了传入的next方法.像上面第一个例子,如果我们只传入一个函数,那么这个函数就会默认当成next方法,就像下面这样他也会正常输出hello和42
import { Observable } from 'rxjs';
const foo = new Observable(subscriber => {
console.log('Hello');
subscriber.next(42);
});
foo.subscribe(
console.log
);
如果将上面的代码再加几行如下面所示
const foo = new Observable(subscriber => {
console.log('Hello');
subscriber.next(42);
});
foo.subscribe(console.log);
foo.subscribe(console.log)
我们可以看到两次重复输出,说明每次subscribe其实都是调用了next传递了固定的数据,不管什么时候执行,都会产生一个新的相同的数据流,这种情况我们称为code Observable
操作符
操作符是rxjs给你提供的一些列方便的纯函数,这些纯函数都会返回一个Observable,他有很多的分类,比如上面所说的of其实就是Creation Operator,再比如还有Join Creation Operators等各种操作符,他能让我们更加方便的使用操作符,具体的操作符大家也可以去官网上查看:rxjs操作符
这边就给大家几个例子大家就懂啦
import {of,range,map,interval,repeat, take} from 'rxjs'
range(5,2).pipe(map(x=>x+2)).subscribe(console.log)
上面的range就是一个创建响应式数据的操作符,他会创建从5开始后面的两个数,pipe相信从字面意思就可以理解了,就是管道的意思,把产生的数据流想象成一个管道,而map也很好理解就是对管道内的每个数据都加2.
import {of,range,map,interval,repeat, take} from 'rxjs'
interval(1000).pipe(take(3))subscribe(console.log)
上述代码interval操作符就是每隔1s执行一次默认会从0开始,take的作用就是再执行完成前最大输出多少个数,所以这边代码会每隔1s输出0,1,2
rxjs提供了好多的操作符,这边就不一一列举了
单播和多播
1、单播
单播故名思意就是只对一个观察者推送,每次推送都会产生一个新的Observable对象
2、多播
多播就是一个数据流可以被多个订阅者订阅,注意这边的数据流是唯一的
import {of,range,map,interval,repeat, take} from 'rxjs'
const source$=interval(1000).pipe(take(3))
source$.subscribe(x=>console.log(1,x))
source$.subscribe(x=>console.log(2,x))
上述代码看似是多播,实在并不是,因为我们之前提到过code Observable,每次subscribe都会产生一个新的Observable对象,所以这并不是一个真正的多播,只不过看上去像是多播。
一直提到code Observable那么肯定还有hot Observable,什么是hot Observable呢?答案就是无论多少个观察者来subscribe,推给观察者的数据都是完全相同的,并不会因为订阅者而创建新的数据流
在rxjs中实现多播其实很简单,rxjs给我们提供了一个subject对象,要将一个code Observable变成hot Observable其实很简单只要外面包装一层就行,里面的数据流仍旧保持唯一,那谁来做这个包装呢,这就是我们的subject的作用了,subject在rxjs中是个双面人,他既有Observable的接口,也有Observer的接口,下面看个subject的例子
import {Subject} from 'rxjs'
const subject = new Subject()
subject.subscribe({ next: (v) => console.log('observerA: ' + v) });
subject.subscribe({ next: (v) => console.log('observerB: ' + v) });
subject.next(1);
subject.next(2);
可以看到我们subject生成一个Observable有两个观察者订阅了,next则是我们的观察者方法,每次执行next都会进行一个推送,这样就实现了我们rxjs中的多播,如果你看到这里能大致理解了rxjs,那就让我们来看一下rxjs的实际应用吧
使用rxjs实现一个高质量请求
如果我们遇到这样一个情况,如果我们购物时需要选择商品的属性,一般业务复杂度够高的商品计算接口时间都会比较久,如果我们选择了一个属性后由于网络原因接口迟迟不返回,这时候我们又想看另一个属性的价格了,我们点击另一个属性等了一下获取到了第二个属性的价格,但是过了一会儿之后我们之前的第一个请求返回了,这时候价格又跳到了第一个属性的价格,这就出现了一个严重的bug,包括如果属性切换的较快我们就可以使用防抖,让用户不操作多少秒后再把请求发出去,或者说我们第一个属性的请求发出去了,第二个属性的请求发出去之前我们就又切回了第一个这时候我们还需要在发送请求吗,是不是只要等第一个请求就可以了,这边都是可以进行优化的,这边的优化其实就可以使用rxjs来进行很快的一个优化
例子如下:
- useSubject.tsx
import axios from "axios";
import { useMemo } from "react";
import { Subject, switchMap, distinctUntilChanged,debounceTime } from "rxjs";
//先执行快的,在执行慢的.
const useSubject = () => {
const request = async (id: string) => {
const res = await axios.get(
`https://www.fastmock.site/mock/3e768256bc5d9ee436dfba82d29e9b47/test/test${id}?id=${id}`
);
return res;
};
const subject = useMemo(() => new Subject<string>(), []);
subject
.pipe(
debounceTime(1000),
distinctUntilChanged(),
switchMap((id) => request(id))
)
.subscribe((res) => console.log(res.data, "res"));
return {
subject,
};
};
export default useSubject;
- index.tsx
import React, { ReactHTMLElement } from "react";
import { Subject } from "rxjs";
import { Input } from "antd";
import axios from "axios";
import useSubject from "./useSubject";
function App() {
const { subject } = useSubject();
const onChange = (e: { target: { value: string } }) => {
subject.next(e.target.value);
};
return <Input style={{ width: 200 }} onChange={onChange} />;
}
export default App;
例子是在react项目中写的,这边我们封装成了一个hook的形式,可以看到如果我们对我们的subject加了debounceTime,就是防抖,还有个distinctUntilChanged的操作符,这个操作符可以让我们在1000时间内判断值是否修改,如果1s内值未修改并不会发送我们能的请求,switchMap则是让我们每次请求都获取到最新的数据,不会因为先发出的接口返回的慢而导致数据展示错误问题,在看我们的index.tsx每次input变化后我们就会执行subject的next方法把值传入让其通知观察者