前言
在学习 Nest 中的 interceptors 时,可以看到很多 RxJS 语法的代码,也可以在 nest 项目中看到 rxjs 的依赖项:
那么 RxJS 是什么呢?
根据官网描述,RxJS 是使用可观察序列(observable sequences)来编写异步的和基于事件的程序的一个库,可以把 RxJS 当作用于处理事件的 Lodash。
Think of RxJS as Lodash for events.
官网说了很多的概念:Observable、Observer、Subscription、Operators、Subject、Schedulers等等。
在有了大概的理解后,从下面几个例子出发去探索学习下,最后,再回头来看相关的概念,便能一目了然。
举个例子
首先,从一个简单的例子出发:假如我们想在点击页面的时候打印 Clicked! ,如何实现呢?
- JS 实现很简单,注册一个事件监听器即可:
document.addEventListener('click', () => console.log('Clicked!'));
- 使用 RxJS 的话,是借助 fromEvent 和 subscribe 去实现的:
import { fromEvent } from 'rxjs';
fromEvent(document, 'click')
.subscribe(() => console.log('Clicked!'));
咋一看有点熟悉,还是有 DOM 对象 document 和相应的点击事件 'click',但是写法明显是不一样的。这里使用了 RxJS 的 fromEvent() 函数,实际上是对于给定的 DOM 对象创建了一个 Observable
。
那么 Observable 是什么呢?
查看文档可知,Observable 是RxJS
的基础和核心,是一个表示随时间推移的异步事件流的对象。Observable 类上面有很多常用的属性和方法,比如 subscribe(), pipe() 等等。
/**
* A representation of any set of values over any amount of time. This is the most basic building block
* of RxJS.
*
* @class Observable<T>
*/
export class Observable<T> implements Subscribable<T> {
/**
* @constructor
* @param {Function} subscribe the function that is called when the Observable is
* initially subscribed to. This function is given a Subscriber, to which new values
* can be `next`ed, or an `error` method can be called to raise an error, or
* `complete` can be called to notify of a successful completion.
*/
constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
if (subscribe) {
this._subscribe = subscribe;
}
}
subscribe(observerOrNext?: Partial<Observer<T>> | ((value: T) => void)): Subscription;
subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;
pipe(): Observable<T>;
pipe<A>(op1: OperatorFunction<T, A>): Observable<A>;
// ...
}
fromEvent(), subscribe()
在上面的例子,我们可以看到使用了fromEvent()
,看文档可知是对于给定的 target 对象创建一个 Observable,当监听到某个事件 eventName 触发时会触发。
export function fromEvent<T>(
target: JQueryStyleEventEmitter<any, T> | ArrayLike<JQueryStyleEventEmitter<any, T>>,
eventName: string
): Observable<T>;
export function fromEvent<T, R>(
target: JQueryStyleEventEmitter<any, T> | ArrayLike<JQueryStyleEventEmitter<any, T>>,
eventName: string,
resultSelector: (value: T, ...args: any[]) => R
): Observable<R>;
// Creates an Observable that emits events of a specific type coming from the given event target.
此外,还使用了subscribe()
,字面上是订阅的意思,也就是触发 Observable 去执行某个方法。而且触发 subscribe 的实际时机是 Observable 开始工作的时候,并非创建的时候。
subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;
// Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.
// ... calling `subscribe` is actually the moment when Observable starts its work, not when it is created.
subscribe() 可以有 next, error, complete 三个传参,正常情况下,Observable 数据流会传入 next 函数,当结束时会触发 complete 函数。举个例子:
of(10, 20, 30)
.subscribe(
value => console.log('next:', value),
err => console.log('error:', err),
() => console.log('the end'),
);
// 打印了:
// next: 10
// next: 20
// next: 30
// the end
pipe(), scan(), filter(), map()
pipe()
是 RxJs 提供的一个函数,可以组合多个操作符,以对数据流进行处理。这些操作符会依次对数据流进行处理。
举个例子,如果点击页面的时候同时提示点击次数,可以这样实现:
fromEvent(document, 'click')
.pipe(scan((count) => count + 1, 0))
.subscribe((count) => console.log(`Clicked ${count} times`));
scan() 类似于 js 中的 reduce() 函数,传参处理函数和初始值。在上面的代码中,我们对于每次数据流的数值,经过 +1 操作后进行累加。
这里我们不能看出,使用 RxJS 具有一个显著的优点,那就是可以通过使用纯函数
隔绝变量的状态,使得代码的健壮性更高。也就是说,这里的 scan 函数的参数是一个纯函数,是在纯函数中去统计点击次数,因此次数 count 不会暴露出来,就不易受到外部的影响。
此外,pipe() 还可以组合更多的操作符:
of(1,2,3,4)
.pipe(
filter(x => x % 2 === 0),
map(x => x + x),
)
.subscribe(x => console.log(x)) // 4 8
代码中,我们还可以看到这两个操作符:
- filter() 操作符:如字面意思那样,可以过滤不符合条件的数据流。
- map() 操作符:类似于 js 中的 map 函数,可以对于数据流中的每个数值进行处理后再返回数据流中。
interval(), take()
我们可以使用interval(time)
实现类似于定时器的效果,每隔时间 time(单位 ms) 触发生成一个非负整数。
接着,使用take(num)
进行判断,num 指定了最大的触发值(不包含该数值)。
因此,下面的代码中只会打印出 0 ~ 3 。
const numbers = interval(1000);
const takeFourNumbers = numbers.pipe(take(4));
takeFourNumbers.subscribe(x => console.log('Next: ', x));
// Logs:
// Next: 0
// Next: 1
// Next: 2
// Next: 3
tap(fn)
这个操作符的功能是执行副作用操作,但不会改变数据流。函数 fn 可以接收到数据流中的数值。
举个例子:
of(Math.random())
.pipe(
tap(console.log),
map(n => n > 0.5 ? 'big' : 'small')
)
.subscribe(console.log);
// 0.003072992334982372
// small
这里会打印随机数字,并进一步与 0.5 判断大小。
后记
本文介绍了 RxJS 中常用的操作符及其示例,看到这里我们对于 RxJS 语法应该有了一定的掌握了,此时再去结合 nest 中 interceptors 的使用就问题不大了。