rxjs学习指北
官网:rxjs7
学习:RXJS5
使用入门:30天学会rxjs
Observable
概念
可以持续生成数据的生产者
可以把Observable想象成去饭店消费排队的客人,都是过来接受服务的并且大家来的时间不能确定
总的来说,只要搞懂,如何去创建、订阅、执行数据队列这几个玩意就入了门,那么我们来看看这些东西的真面目吧。
创建
创建的方式多种多样,有创建固定个数的(create、构造函数、of、from)方法,也有生成无限个数的方法(事件监听、interval)
// 0、普通的create创建【已废弃】
let stream$ = rxjs.Observables.create((observer) => {
observer.next(4);
observer.next(1);
observer.error('some error');
observer.complete();
})
// 1、通过构造函数创建 【替代方法】
const observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
// 2、点击流
const click = fromEvent(document, "click", {
capture: true
});
const positions = click.pipe(map((ev) => ev.clientX));
positions.subscribe((x) => console.log(x));
// 3、创建函数 具体看官网
// of(1,2,3)
// from([1,2,3])
// interval(1000)
// EMPTY
// defer(() => {isOk?of(1,2,3):interval(1000)}) 在需要时再去生成对应的 observable
// 轮询处理请求 demo
const obser = new Observable(obs => {
setInterval(() => {
fetch('https://codesandbox.io/s/gracious-hill-xnlxs?file=/src/index.ts').then(res => res.text())
.then(res => obs.next(res));
},2000)
})
obser.subscribe((e) => console.log(e))
数据流是有了,接下来就是需要如何去处理数据了。快马加鞭来看看订阅方法
订阅
简单来说就是对每个生产者的处理方法清单
subscribe函数,分别对应
Observernext、error、complete 调用的三个方法
observable.subscribe(fnNext, fnError?, fnComplete?)
延续饭店的例子,可以想象是处理排队顾客的小二,下一个正常客人来时执行(next),遇到砸场子的客户执行(报错Error),结束接客时执行(complete)【被砸场子和结束接客都将无法接收下一个客人了】。
// 其中 stream$ 指的是 observable 对象
const sub = stream$.subscribe(
(value) => console.log('Value', value),
(err) => console.log('err', err),
() => console.log('complete')
);
// or
const sub = stream$.subscribe({
next: value => console.log('A next: ' + value),
error: error => console.log('A error: ' + error),
complete: () => console.log('A complete!')
});
// 为了健壮性来说,对于有注册的地方就必须要有对应的注销
setTimeout(() => {
sub.unsubscribe();
}, 3000)
- 有时对于数据流有多个处理清单时,往往想要放在一起进行注销
const sub1 = stream$.subscribe(next: value => console.log('A next: ' + value));
const sub2 = stream$.subscribe(next: value => console.log('A next: ' + value));
// 添加成捆绑操作
sub1.add(sub2);
// 此时注销将会将子Subscription也注销掉
sub1.unsubscribe() // sub2.unsubscribe()也会被执行
// 撤销捆绑
sub1.remove(sub2)
至此,我们有了数据流(创建),也有了处理清单(订阅),接下来直接跑就对了
执行
实际上跑也不是后面控制的,在创建时就已经设计好了情况
由于创建时区分了有限数据和无限数据那么情况也肯定是不一样的
- 有限数据 & 通过构造函数
可以手动主观的调用 上面清单中的 next error complete 方法【多是用来观察情况】
let stream$ = new Observable((observer) => {
observer.next(4); // 4
observer.next(1); // 1
observer.error('some error') // error: some error
observer.complete();// complete
observer.next(1);// (不运行)
})
stream$.subscribe({
next: value => console.log('A next: ' + value),
error: error => console.log('A error: ' + error),
complete: () => console.log('A complete!')
});
- 其他数据
则无法手动触发next error complete 方法,按顺序执行next方法,报错时error方法处理,结束时调用complete方法(可能不存在结束)
Operator
操作符,将数据流按操作符变化
延续上面的例子: 将来的顾客们进行些许变化,比如只能有钱人来吃(filter),吃的人得先去洗手(map),前5名客人(take),最后一个客人(last)
ps:operator忙忙多,很多情况都无法确定对应的operator,可用此判断 rxjs决策树
pipe方法
对于Observable来说,还有个不得不提的方法 pipe
对于数据流变化可能会有很多,拿上面的例子来说,按函数式的逻辑得写成
take(map(filter()))的形式『不优雅,难看』等字眼立马浮现。因此pipe管道的出现就是为了将所有的Operator进行串联,并以流的形式进行操作传递
observable.pipe(
filter(x => x.money === 'rich'),
map(x => x.wash = true),
take(5)
)
Subject
常用于广播形式,可添加多个订阅方法,一次next触发。类似于看直播
相比于其他是1对1形式比如
每次subscribe都是最新的,类似于看视频
- 既能作为像之前Observable构造函数一样,能够调用.next调用下一个,进行广播
// 普通的subject
const subject = new Subject();
const observerA = {
next: value => console.log('A next: ' + value),
error: error => console.log('A error: ' + error),
complete: () => console.log('A complete!')
}
const observerB = {
next: value => console.log('B next: ' + value),
error: error => console.log('B error: ' + error),
complete: () => console.log('B complete!')
}
subject.subscribe(observerA);
subject.subscribe(observerB);
subject.next(1);
// "A next: 1"
// "B next: 1"
subject.next(2);
// "A next: 2"
// "B next: 2"
- 也能作为observer被其他observable订阅
import { Subject, from } from 'rxjs';
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
const observable = from([1, 2, 3]);
observable.subscribe(subject); // You can subscribe providing a Subject
// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
注意:使用情况条件要判断好:『若每次请求结果都不同,则需要使用subject』
一般搭配connect/share [operaters] 去搭配操作源 ps: 之前广播使用的 multicast(operator) - 已被废弃【迭代太快了】
- 对于connect来说,接收多个observer
// 函数的参数是一个observable,可以将广播数据进行多种方式变化
const result = interval(1000).pipe(
take(6),
map((x) => Math.random()), // side-effect
connect(
(item) => merge(
item.pipe(map((x)=>"A: " + x)),
item.pipe(map((x)=>"B: " + x))
)
)
);
result.subscribe((x) => console.log(x));
-
share() 等价于
pipe(connect(() => new Subject()), refCount())refCount使多播的 Observable 在第一个订阅者到达时自动开始执行,并在最后一个订阅者离开时停止执行。
const result = interval(1000).pipe(
take(6),
map((x) => Math.random()), // side-effect
share()
);
const subA = result.subscribe((x) => console.log("A: " + x));
const subB = result.subscribe((x) => console.log("B: " + x));
setTimeout(() => {
subA.unsubscribe()
}, 2000)
Scheuler
调度器,控制同步 & 异步调用
// 调节可控成同步/异步
const observable3 = new Observable((observer) => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
}) // 不加pipe这句话时,为同步 0 1 2 3 4
.pipe(observeOn(asyncScheduler)); // 此时为异步 0 4 1 2 3
console.log("0");
observable3.subscribe(subscribeObj);
console.log("4");
Operator demo
两个用到的demo列举,其他的直接看 弹珠图 / 决策树更快~
takeUntil
两个observer,直到一个被触发时停止
const observable2 = interval(1000);
const click = fromEvent(document, "click", {capture: true});
const example = observable2.pipe(takeUntil(click))
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
concatAll
将多维observable变为一维 看弹珠图即可了解
我们来模拟一下拖动事件
-
流程: 鼠标按下 -》 鼠标拖动 -》 鼠标抬起
-
三个observable,鼠标按下开始监听拖动事件,直到抬起结束监听拖动
-
对于mouseDown在concatAll前:
[[mouseMove1-1,...,mouseMove1-n], [mouseMove2-1,...,mouseMove2-n], [mouseMove3-1,...,mouseMove3-n]...]
-
concatAll后:
[mouseMove1-1,...,mouseMove1-n,mouseMove2-1,...,mouseMove2-n,mouseMove3-1,...,mouseMove3-n,...]
const mouseUp = fromEvent(body, "mouseup");
const mouseMove = fromEvent(body, "mousemove");
const mouseDown = fromEvent(dragDOM, "mousedown").pipe(
map(() => mouseMove.pipe(takeUntil(mouseUp))),
concatAll(),
map((event) => ({ x: event.clientX, y: event.clientY }))
);
mouseDown.subscribe((pos) => {
dragDOM.style.left = pos.x + "px";
dragDOM.style.top = pos.y + "px";
console.log(pos);
});