RxJS 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序。它提供了一个核心类型 Observable,附属类型 (Observer、 Schedulers、 Subjects) 和受 [Array#extras] 启发的操作符 (map、filter、reduce、every, 等等),这些数组操作符可以把异步事件作为集合来处理。
可以把 RxJS 当做是用来处理事件的 Lodash 。
可以把 RxJS 当做是用来处理异步的 Promise。
1. Rxjs的概念介绍
Observable
Observable是RxJS的核心。一个Observable代表一个可调用的未来值或事件的集合。创建Observable后,它并不会立即执行,而是等待被订阅。只有当Observable被一个Observer订阅后,它才开始发射数据。
Observer
Observer是一个对象,包含了一些回调函数,这些函数定义了如何处理Observable发出的数据。Observer的三个主要回调函数分别是:next
、error
和complete
。
const observer = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
Subscription
Subscription是表示可清理资源的对象,通常是Observable的执行。当你订阅一个Observable时,你会得到一个Subscription对象。你可以调用它的unsubscribe()
方法取消订阅,停止Observable发射数据。
Operators
Operators是方法,允许你以声明式的方式处理集合。操作符可以被链式调用,每一个操作符都会接收一个Observable,并返回一个新的Observable,这允许操作符被组合使用。
Subject
Subject是一种特殊类型的Observable,它允许将值多播到多个Observer。普通的Observable是单播的(每个订阅的Observer拥有Observable独立的执行)。
基本使用
安装
首先,你需要安装RxJS库:
npm install rxjs
// yarn add rxjs
简单使用
假设有一个输入框,你希望根据用户的输入实时获取搜索结果。你可以创建一个Observable来监听输入框的input
事件。
import { fromEvent } from 'rxjs';
import { map, debounceTime } from 'rxjs/operators';
export default {
mounted() {
const input = document.querySelector('#searchInput');
const input$ = fromEvent(input, 'input').pipe(
debounceTime(300),
map(event => event.target.value),
// 这里可以继续加入更多的操作符处理数据
);
input$.subscribe(value => {
console.log(`Searching for ${value}`);
// 可以在这里调用API,根据value来获取数据
});
}
}
如何创建一个Observable
1. 使用new Observable()
构造函数
import { Observable } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next('Hello');
subscriber.complete();
});
observable.subscribe(observer);
2. 使用创建操作符of
of
可以将一些列的值转换成Observable。
import { of } from 'rxjs';
const observable = of(1, 2, 3);
observable.subscribe(observer);
3. 使用创建操作符from
from
可以将类数组、可迭代对象、Promise等转换成Observable。
import { from } from 'rxjs';
const array = [1, 2, 3];
const observable = from(array);
// 将Promise转化为Observable
const promise = Promise.resolve(true);
const observable$ = from(promise);
observable$.subscribe(res => {
console.log(res); // true
)
// 也可以将Observable转化为Promise
const array2 = [1, 2, 3];
const observable2$ = from(array);
const promiseInstance = observable2$.toPromise();
promiseInstance.then(res => {
console.log(res)
}
4. 使用fromEvent
创建Observable
fromEvent
可以将DOM事件转换成Observable。
import { fromEvent } from 'rxjs';
const button = document.querySelector('button');
const observable = fromEvent(button, 'click');
操作符
什么是操作符?
操作符是 Observable 类型上的方法,比如 .map(...)
、.filter(...)
、.merge(...)
,等等。当操作符被调用时,它们不会改变已经存在的 Observable 实例。相反,它们返回一个新的 Observable ,它的 subscription 逻辑基于第一个 Observable 。
操作符是函数,它基于当前的 Observable 创建一个新的 Observable。这是一个无副作用的操作:前面的 Observable 保持不变。 好比是内置的工具函数,有点类似于lodash。
操作符本质上是一个纯函数 (pure function),它接收一个 Observable 作为输入,并生成一个新的 Observable 作为输出。订阅输出 Observable 同样会订阅输入 Observable 。在下面的示例中,我们创建一个自定义操作符函数,它将从输入 Observable 接收的每个值都乘以10:
function multiplyByTen(input) {
var output = Observable.create(function subscribe(observer) {
input.subscribe({
next: (v) => observer.next(10 * v),
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
return output;
}
var input = Observable.from([1, 2, 3, 4]);
var output = multiplyByTen(input);
output.subscribe(x => console.log(x));
输出
10
20
30
40
操作符的基本使用
1. 使用map
操作符将每个值加倍
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
const numbers = from([1, 2, 3, 4, 5]);
const doubled = numbers.pipe(
map(value => value * 2)
);
doubled.subscribe(x => console.log(x));
// 输出: 2, 4, 6, 8, 10
2. 使用filter
操作符过滤偶数值
import { from } from 'rxjs';
import { filter } from 'rxjs/operators';
const numbers = from([1, 2, 3, 4, 5]);
const evens = numbers.pipe(
filter(value => value % 2 === 0)
);
evens.subscribe(x => console.log(x));
// 输出: 2, 4
操作符的分类
操作符按照分类有几十上百种,不过我们不用全部理解,学习常用的就够用了。 操作符有着不同的用途,它们可作如下分类:创建、转换、过滤、组合、错误处理、工具,等等。在下面的列表中,你可以按分类组织好的所有操作符。
创建操作符
ajax
bindCallback
bindNodeCallback
create
defer
empty
from
fromEvent
fromEventPattern
fromPromise
generate
interval
never
of
repeat
repeatWhen
range
throw
timer
转换操作符
buffer
bufferCount
bufferTime
bufferToggle
bufferWhen
concatMap
concatMapTo
exhaustMap
expand
groupBy
map
mapTo
mergeMap
mergeMapTo
mergeScan
pairwise
partition
pluck
scan
switchMap
switchMapTo
window
windowCount
windowTime
windowToggle
windowWhen
过滤操作符
debounce
debounceTime
distinct
distinctKey
distinctUntilChanged
distinctUntilKeyChanged
elementAt
filter
first
ignoreElements
audit
auditTime
last
sample
sampleTime
single
skip
skipLast
skipUntil
skipWhile
take
takeLast
takeUntil
takeWhile
throttle
throttleTime
组合操作符
combineAll
combineLatest
concat
concatAll
exhaust
forkJoin
merge
mergeAll
race
startWith
switch
withLatestFrom
zip
zipAll
多播操作符
错误处理操作符
工具操作符
do
delay
delayWhen
dematerialize
finally
let
materialize
observeOn
subscribeOn
timeInterval
timestamp
timeout
timeoutWith
toArray
toPromise
条件和布尔操作符
数学和聚合操作符
使用Rxjs解决开发中常见的场景
1. 输入框的防抖
防抖是指在一段连续时间内,只执行一次事件。常用于输入框搜索,避免频繁发送请求。
import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';
const input = document.querySelector('#searchInput');
const input$ = fromEvent(input, 'input').pipe(
debounceTime(300),
map(event => event.target.value)
);
input$.subscribe(value => {
console.log(`Searching for ${value}`);
// 发送搜索请求
});
2. 按钮的节流
import { fromEvent } from 'rxjs';
import { throttleTime, map } from 'rxjs/operators';
const button = document.querySelector('#myButton');
const buttonClicks = fromEvent(button, 'click').pipe(
throttleTime(1000)
);
buttonClicks.subscribe(() => console.log('Button clicked with throttle!'));
3. 接口请求失败重试
在与API交互时,网络或服务问题可能导致请求失败。使用retry
操作符可以自动重试请求。
import { ajax } from 'rxjs/ajax';
import { retry, catchError } from 'rxjs/operators';
const apiData = ajax('/api/data').pipe(
retry(3), // 重试3次
catchError(error => of(error))
);
apiData.subscribe(data => console.log('Data: ', data));
4. 接口错误处理
import { of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';
const apiData = ajax('/api/data').pipe(
catchError(error => {
console.error('Error:', error);
return of({ error: true, message: 'Failed to fetch data' });
})
);
apiData.subscribe(data => console.log('Data: ', data));
5. 操作符组合
考虑以下场景:通过接口获取到应用列表,过滤掉未发布的应用,然后根据每一条数据的key,从后端获取到clientId。
// 请求参数
const params = {
filterText: '',
modelType: 'APP',
appModelId: ''
}
// 分页
const pageLink = new PageLink(10, 0);
// 接口请求
this.backendHttpService.queryAppDesignModels(pageLink, params)
.pipe( // 管道
// map 过滤掉未发布的应用,也可以直接用filter操作符
map(appinfos => {
appinfos.data = appinfos.data.filter(item => item.state !== 'UNDEPLOY');
return appinfos;
}),
// switchMap 将源 Observable 的每个值映射到一个新的 Observable
switchMap(apps => from(apps.data)),
// concatMap `concatMap` 会按照源 Observable 发出值的顺序订阅和发出这些映射的 Observable
concatMap((arr: { key: string }) => this.ucService.getClientUUID(arr.key)),
// 拿到后端返回数据
map((appinfos) => appinfos.clientId),
// 将请求接口获得的数据转化为数组
toArray(),
// tap 无副作用的操作符,可以用来做其他的事情、调试等
tap(res =>{
this.clientIdArr = res;
console.log(res); // clientId数组
},
// 过滤掉假值
map((appinfos) => appinfos.filter(Boolean)),
)
.subscribe({
next: (appList) => {
console.log(appList)
}
})
通过这些示例,我们可以看到Rxjs在处理异步和事件驱动编程时的强大能力。它提供了一种更加声明式和可组合的方式来处理复杂的异步逻辑,使得我们的代码更加清晰和可维护。由第五条示例可以看出,操作非常强大,灵活组合可以实现非常复杂的场景,这是Promise所不能比拟的,老铁们赶紧用起来。
参考资料
本文只是一个抛砖引玉的作用,只是大概的基本概念,希望大家可以学习并使用这个库(Angular框架深度使用Rxjs)。提升自己的代码质量。
Rxjs的中文文档:cn.rx.js.org/manual/over…