Rxjs 入门教程

668 阅读7分钟

Rxjs 核心对象 Observable

基于生产者与消费者的设计理念,可以返回一个或多个值。Observable 使用惰性计算的方式来产生数据,subscribe 消费数据之前不会被运行。

通过 npm 安装 rxjs

npm install rxjs

生产数据的方式:next

终止数据生产方式:compelte

消费数据的方式:subscribe

异步生成数据方式:setTimeout

应用案例:批量读写数据

import { Observable } from 'rxjs';

const producer = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {
    subscriber.next(4);
  //  subscriber.complete();
    subscriber.next(5);
  }, 1000);
});
const consumer = {
  next(x) { console.log('got value ' + x); },
  error(err) { console.error('something wrong occurred: ' + err); },
  complete() { console.log('done'); }
}

console.log('just before subscribe');
producer.subscribe(consumer);
console.log('just after subscribe');

Rxjs Observable 创建操作符

  1. fromEvent 通过事件创建 Observable 对象
import { fromEvent } from 'rxjs';

fromEvent(document, 'click').subscribe(() => console.log('Clicked!'));
  1. of ,from ,interval ,timer ,range

of 一个或多个值创建 Observable 对象

from 数组创建Observable 对象

interval 用来创建定时 job ,配置参数指定间隔时间

timer 与 interval 相似,但是可以指定 延迟执行时间

range 指定 闭区间的 start 与 end ,可以用来代替 for 循环

import { of, from,range } from 'rxjs';
import { interval } from 'rxjs';
import { timer } from 'rxjs';

of(1, 2, 3).subscribe(x=>console.log(x))

from([4,5,6]).subscribe(x=>console.log(x))

interval(1000).subscribe(x=>console.log(x));

timer(3000, 1000).subscribe(x=>console.log(x));

range(1, 10).subscribe(x=>console.log(x));

Rxjs Observable 转换操作符

  1. pipe 符号,用来包裹多种转换操作符,像 map , filter ,concat ,merge 等 。使用设计 pipe 包裹的原因是增加程序可读性
obs.pipe(
  op1(),
  op2(),
  op3(),
  op3(),
)
  1. 数据预处理之 map

与 java8 特性 stream 的 map 使用方式相同,通过 map 在数据被消费之前放大两倍。

import { range } from 'rxjs';
import { map } from 'rxjs/operators';
range(1, 10).pipe(
    map(x => x * 2)
).subscribe(x => console.log(x));
  1. 数据预处理之 filter

把偶数过滤出来

import {  range } from 'rxjs';
import {  filter } from 'rxjs/operators';
range(1, 10).pipe(
    filter(x=>x%2===0)
).subscribe(x => console.log(x));
  1. 数据预处理之 find

find 与 filter 类似,但是 find 找到满足条件的数据时候会立即返回,find 返回单值,filter 返回数组。下面的 demo 只会返回 2

import { of, range } from 'rxjs';
import {  find } from 'rxjs/operators';
range(1, 10).pipe(
    find(x => x % 2 === 0)
).subscribe(x => console.log(x));

Rxjs Observable 数值计算与聚合操作符

  1. count ,max , min

Count 统计符合条件数据个数,例如 统计偶数个数


import { count } from 'rxjs/operators';

const numbers = range(1, 10);
//偶数个数
const result = numbers.pipe(count(i => i % 2 === 1));
result.subscribe(x => console.log(x));
//寻找最大值 min 同理
const a = numbers.pipe(max());
a.subscribe(x => console.log(x));
  1. reduce , scan

seed 指定累加初始值 , 当reduce 结束后 返回结果,并且只有一个结果,与 scan 不同,scan 将会把每步计算的结果值输出。

import {  range } from 'rxjs';
import {  reduce } from 'rxjs/operators';
const numbers = range(1, 10);
const seed:number=0;
range(1, 10).pipe(reduce((a, b) => a + b, seed)).subscribe(x => console.log( x));
range(1, 10).pipe(scan((a, b) => a + b, seed)).subscribe(x => console.log( x));

Rxjs Observable 连接操作符

  1. combineLatest

组合多个Observable 的最新发出值。


import { timer } from 'rxjs';
import { combineLatest } from 'rxjs';
const firstTimer = timer(0, 1000); // emit 0, 1, 2... after every second, starting from now
const secondTimer = timer(500, 1000); // emit 0, 1, 2... after every second, starting 0,5s from now
const combinedTimers = combineLatest([firstTimer, secondTimer]);
combinedTimers.subscribe(value => console.log(value));

如下图共有两个 Obaservale 对象,这两个对象会异步的发出值,第一个Observable对象将会按照一定时间间隔,按照顺序发出 a->b->c->d->e 。另一个 Observale 对象 1->2->3->4.

combineLatest 会不停的监控这两个对象发出的值,当任何一个observable 对象一有新值发出时,就会同时取出这这两个最新的值,包裹到一起,发出。从而就如下图所示。

给这两个 observable 对象 起两个名字,分别为 A , B

当 A 发出 a值的时候,B还没有发出 1 ,这时combineLatest 不会发出值,当B 发出1 的时候,combineLatest 立马就监控到了,因此同时向 A 和 B 取值,得到 a1 ,同理 得到 b1 b2 等。 image-20200703135845051.png

2. combineAll

有的时候会产生多个 Observable ,combineAll 用来结合这些 Observable ,将返回值组装到数组中。

当所有的 Observable 全部生成的时候,再使用 combineLatest 来订阅这些 Observable 发出的值。

import { combineAll } from 'rxjs/operators';
import { range } from 'rxjs';
range(1,2).pipe(
    map((e)=>take(5)(interval(300))),
    combineAll()
).subscribe(x => console.log(x));

image-20200703143647351.png

  1. concatMap , mergeMap , switchMap , exhaustMap

有时候想要把两个 Observable 的值 前后拼接起来**(强调两个Observable 输出值的顺序 ),打平输出 ,这个时候可以使用 concatMap ,这里是同步的,第一个Observable 会挂阻塞线程,除非对顺序有要求否则慎用**。

mergeMap 则不会,多个Observable 异步执行。

interval 起一个间隔 Observable 对象的 job ,当 4秒后,终止 job 。

concatMap 输出序列为 [1,2,3,4,1,2,3,4]

mergeMap 输出序列为[1,1,2,2,3,3,4,4]

import { interval } from 'rxjs';
import { mergeMap , concatMap, take } from 'rxjs/operators';
const result1 = of(1,2).pipe(
    concatMap(ev => interval(1000).pipe(take(4)))
  );
result.subscribe(x => console.log(x));

const result2 = of(1,2).pipe(
    mergeMap(ev => interval(1000).pipe(take(4)))
  );
result2.subscribe(x => console.log(x));

switchMap 是有两个操作符组成, switch 与 map ,switch 负责切换源Observable 最新发出的Observable发出的数据。历史的Observable将会被取消订阅。

下面的例子中,源Observable每秒中通过map 生成一个 Observable ,且每 300 毫秒发出一个值。

通过 switchAll 决定只有一个 Observable 能够存活,当有最新生成的Observable 在 300 毫秒后发出一个值后,其他的 Observable 将会被取消订阅并销毁。

将 switchAll ,map 结合 为 switchMap

import { map, switchAll } from 'rxjs/operators';

interval(1000).pipe(
    map(e=>take(5)(interval(300))),
    switchAll()
).subscribe(x => console.log(x));

结合后的代码

interval(1000).pipe(
    switchMap(e=>take(5)(interval(300)))
).subscribe(x => console.log(x));

如下图可以看到,两个 Observable 对象 ,A 对象 会按照时间间隔,发出 a->b->c->d , B 对象会发出 e->f ->g.

但是时间间隔不同,当 A 对象开始发出值的时候, B 还没有发出值,这个时候就不会切换 observable 源,因此源还是 A 对象,但是当 B 对象发出 e 值的时候,switch 方法监控到了,就会立马切换源到 B 对象,因此接下来的序列是 e -> f ->g

![image-20200702231910850](Rxjs 入门.imgs/image-20200702231910850.png)

exhaustMap 是由exhaust 与 map 两个操作符组成

当源Observable 发出 Observable 项的时候,第一个发出Observable的产生数据没有结束的时候,其他 Observable 发出值的动作将会被取消。

exhaustMapswitchMap 相反,如果历史的 observable 没有执行完,下一个 observable 即使已经发出值了 ,将会被取消,只有当 第一个 observable 执行完成之后,下一个才能被执行。这种场景可以应用在 click 事件上,用户重复点击场景。

image-20200703130232768.png

程序产生序列为0 1 2 3 4 0 1 2 3 4。如果发出的Observable 不结束,下一个Observable 要调起时候会直接被取消掉。

import { map, exhaust } from 'rxjs/operators';
import {  interval } from 'rxjs';
import {  take } from 'rxjs/operators';
interval(1000).pipe(
    map(e=>take(5)(interval(300))),
    exhaust()
).subscribe(x => console.log(x));

//合并操作符
interval(1000).pipe(
    exhaustMap(e=>take(5)(interval(300))),
).subscribe(x => console.log(x));

Scheduler

调度器的目的是可以让你去选择以何种方式去执行 observable , 同步,队列,异步。同步的话可以不设定 scheduler ,如果想以异步的方式可以选择 asapScheduler 与 asyncScheduler 两种方式。

两者的区别在于 asap == as soon as possible , 启动的是 微任务,asyn 启动是宏任务,微任务的优先级会高与宏任务,这里涉及到 event loop 的一些原理,感兴趣的可以查询相关资料。仅仅是使用的话,这些调度器就够用了。

调度器 目的
null 不传递任何调度器的话,会以同步递归的方式发送通知。用于定时操作或尾递归操作。
queueScheduler 当前事件帧中的队列调度(蹦床调度器)。用于迭代操作。
asapScheduler 微任务的队列调度,它使用可用的最快速的传输机制,比如 Node.js 的 process.nextTick() 或 Web Worker 的 MessageChannel 或 setTimeout 或其他。用于异步转换。
asyncScheduler 使用 setInterval 的调度。用于基于时间的操作符。

指定异步 asyncScheduler ,可以异步执行 Observable

const result=of(1,2).pipe(
    observeOn(asyncScheduler)
)
console.log('start ...')
result.subscribe(x => console.log(x));
console.log('end ...')

//输出
start ...
end ...
1
2