rxjs学习
基本概念
Observable (可观察对象):
表示一个概念,这个概念是一个可调用的未来值或事件的集合。相当于Promise对象,内部可以用于执行异步代码,通过调用内部提供的方法将异步代码执行的结果传递到可观察对象外部。
Observer (观察者):
一个回调函数的集合,它知道如何去监听由 Observable 提供的值。类比then, 方法中的回调函数,用于接收可观察对象中传递出来的数据
Subscription (订阅):
表示 Observable 的执行,主要用于取消 Observable 的执行。类比then方法.
Operators (操作符):
采用函数式编程风格的纯函数 (pure function),使用像 map、filter、concat、flatMap 等这样的操作符来处理集合。
Subject (主体):
相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
Schedulers (调度器):
用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeout 或 requestAnimationFrame 或其他。
讲解一下观察者模式,怕有的人不了解或遗忘了。
观察者模式(observer),又叫发布-订阅模式(publish/subscribe)。这里说下,发布-订阅模式其实有的说他是一种消息范式。并不是一种设计模式。在维基百科中也是有说明的,不过在后来的vue和react中都有使用,可能大家也就默认了吧。但是影响不大。个人举个例子说明一下:
你去超市买烟,但是呢,烟没货了,你就跟老板说:“老板,等烟到货了,记得通知我”。然后烟到货后,老板通知你,你就来买了。在代码中,也是这样,你不知道某个事件何时触发,只能在其触发时去执行相应的逻辑。所以这里面,老板那里是个observable,可观察的对象,而你呢,就是一个observer,观察者。你们这件这种通知的关系,就是一种订阅的关系。
//定义烟店的老板
const smoke_boss = new Observable(todo => {
//等烟到货了,通知买家
setTimeout(() => {
todo.next("烟到货了")
}, 1000);
})
//定义买家1
const buy_one = {
next: function (data:any) {
console.log(data)
}
}
//建立关系。也就是订阅
smoke_boss.subscribe(buy_one)
当Observable产生一个新值时,会通知 observer 的 next(),而当捕获失败可以调用 error()。
当Observable被订阅后,除非调用observer的 complete() 或 unsubscribe() 取消订阅两情况以外;会一直将值传递给 observer。
Observable的生产的值允许经过一序列格式化或操作,最终得到一个有价值的数据给观察者,而这一切是由一序列链式operator来完成的,每一个operator都会产生一个新的Observable。而我们也称这一序列过程为:流。
什么是operator?
正如前面说到的,Observable可以链式写法,这意味着我们可以这样:
Observable.fromEvent(node, 'input')
.map((event: any) => event.target.value)
.filter(value => value.length >= 2)
.subscribe(value => { console.log(value); });
下面是整个顺序步骤:
假设用户输入:a
Observable对触发 oninput 事件作出反应,将值以参数的形式传递给observer的 next()。
map() 根据 event.target.value 的内容返回一个新的 Observable,并调用 next() 传递给下一个observer。
filter() 如果值长度 >=2 的话,则返回一个新的 Observable,并调用 next() 传递给下一个observer。
最后,将结果传递给 subscribe 订阅块。
你只要记住每一次 operator 都会返回一个新的 Observable,不管 operator 有多少个,最终只有最后一个 Observable 会被订阅。
不要忘记取消订阅
为什么需要取消订阅
Observable 当有数据产生时才会推送给订阅者,所以它可能会无限次向订阅者推送数据。正因为如此,在Angular里面创建组件的时候务必要取消订阅操作,以避免内存泄漏,要知道在SPA世界里懂得擦屁股是一件必须的事。
unsubscribe
前面示例讲过,调用 subscribe() 后,会返回一个 Subscription 可用于取消操作 unsubscribe()。最合理的方式在 ngOnDestroy 调用它。
ngOnDestroy() {
this.inputSubscription.unsubscribe();
}
takeWhile
如果组件有很多订阅者的话,则需要将这些订阅者存储在数组中,并组件被销毁时再逐个取消订阅。但,我们有更好的办法:
使用 takeWhile()
operator,它会在你传递一个布尔值是调用 next() 还是 complete()。
private alive: boolean = true;
ngOnInit() {
const node = document.querySelector('input[type=text]');
this.s = Observable.fromEvent(node, 'input')
.takeWhile(() => this.alive)
.map((event: any) => event.target.value)
.filter(value => value.length >= 2)
.subscribe(value => { console.log(value) });
}
ngOnDestroy() {
this.alive = false;
}
简单有效,而且优雅。
Subject
如果说 Observable 与 observer 是攻受结合体的话,那么 Subject 就是一个人即攻亦受。正因为如此,我们在写一个Service用于数据传递时,总是使用 new Subject。
@Injectable()
export class MessageService {
private subject = new Subject<any>();
send(message: any) {
this.subject.next(message);
}
get(): Observable<any> {
return this.subject.asObservable();
}
}
当F组件需要向M组件传递数据时,我们可以在F组件中使用 send()。
constructor(public srv: MessageService) { }
ngOnInit() {
this.srv.send('w s k f m?')
}
而M组件只需要订阅内容就行:
constructor(private srv: MessageService) {}
message: any;
ngOnInit() {
this.srv.get().subscribe((result) => {
this.message = result;
})
}
EventEmitter
其实EventEmitter跟RxJS没有直接关系,因为他是Angular的产物,而非RxJS的东西。或者我们压根没必要去谈,因为EventEmitter就是Subject。
EventEmitter的作用是使指令或组件能自定义事件。
@Output() changed = new EventEmitter<string>();
click() {
this.changed.emit('hi~');
}
@Component({
template: `<comp (changed)="subscribe($event)"></comp>`
})
export class HomeComponent {
subscribe(message: string) {
// 接收:hi~
}
}
上面示例其实和上一个示例中 MessageService 如出一辙,只不过是将 next() 换成 emit() 仅此而已。
结论
RxJS最难我想就是各种operator的应用了,这需要一些经验的积累。
RxJS很火很大原因我认还是提供了丰富的API,以下是摘抄:
创建数据流:
单值:of, empty, never
多值:from
定时:interval, timer
从事件创建:fromEvent
从Promise创建:fromPromise
自定义创建:create
转换操作:
改变数据形态:map, mapTo, pluck
过滤一些值:filter, skip, first, last, take
时间轴上的操作:delay, timeout, throttle, debounce, audit, bufferTime
累加:reduce, scan
异常处理:throw, catch, retry, finally
条件执行:takeUntil, delayWhen, retryWhen, subscribeOn, ObserveOn
转接:switch
组合数据流:
concat,保持原来的序列顺序连接两个数据流
merge,合并序列
race,预设条件为其中一个数据流完成
forkJoin,预设条件为所有数据流都完成
zip,取各来源数据流最后一个值合并为对象
combineLatest,取各来源数据流最后一个值合并为数组
另外最好使用 $ 结尾的命名方式来表示Observable,例:input$。
angular 使用rxjs 监听同级兄弟组件数据变化
angular 的官网给出了父子组件之间数据交互的方法,如ViewChild、EventEmitter
但是如果要在同级组件之间进行数据同步,似乎并没有给出太多的信息。
有时候我们想,在一个组件中修改数据之后,马上反映到另外一个组件中, 或者可能需要调用另外一个组件中的方法,这时候,我们就可以借助于 rxjs 了。
下面源码在 https://github.com/eleven26/angular-observer
1、首先,我们定义一个用于在应用内进行数据交互的 service
import {Injectable} from '@angular/core';
@Injectable()
export class StoreService {
public storageObj = {};
set(key, value) {
this.storageObj[key] = value;
}
get(key) {
return this.storageObj[key];
}
remove(key) {
delete this.storageObj[key]
}
}
2、我们在需要监听数据变化的组件中加入以下代码,此处是直接写在了 AppComponent 中
import {Component, OnInit} from '@angular/core';
import {StoreService} from './store.service';
import {Observable} from 'rxjs/Observable';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
title = 'app works!';
constructor(private storeService: StoreService) {}
ngOnInit() {
let myObserver: Observable<any> = Observable.create((observer) => {
// payload 用以存放不同组件之间需要交互的数据
let payload = this.storeService.get('payload');
if (payload) {
// do something
if (payload['type'] == 'set-title') {
this.title = payload['title'];
}
this.storeService.remove('payload');
}
observer.next();
});
// 保存到公共 service 中,以便不同组件之间可以使用同一个观察者对象
this.storeService.set('myObserver', myObserver);
}
}
上面的代码中,我们定义了一个可观察对象 myObserver,我们在此方法上调用 subscribe 的时候,Observable.create 的回调就会被调用,
然后,我们从 StoreSrevice 中取得需要处理的数据,具体数据格式自由规定,
我们处理完数据之后,把相关数据从 StoreSrevice 中移除
同时,我们需要把 myObserver 保存到 StoreService 中,以便不同组件获取到的是同一个 observer。
3、定义一个 A 组件,用来生产 payload,类似于 生产者-消费者
import {Component, OnInit} from '@angular/core';
import {StoreService} from './store.service';
import {Observable} from 'rxjs/Observable';
@Component({
selector: 'a-component',
template: `
<h1>A Component'title: {{title}}</h1>
<input type="text" (keyup)="change($event.target.value)">
`,
styles: []
})
export class AComponent implements OnInit{
title = 'a title';
public myObserver: Observable<any>;
constructor(private storeService: StoreService) {}
ngOnInit() {
this.myObserver = this.storeService.get('myObserver');
}
change(new_title) {
this.title = new_title;
this.storeService.set('payload', {
type: 'set-title',
title: new_title
});
this.myObserver.subscribe(() => {});
}
}
我们在该组件输入 title 的时候,发现 AppComponent 的 title 也发生了相应的变化。
这样一来,我们的效果就实现了。