RxJS 浅析

90 阅读11分钟

下载.png

Rxjs是什么

RxJS 是使用 Observables 的响应式编程的库,它使编写异步或基于回调的代码更容易(官方文档),是 Reactive Extensions for JavaScript 的缩写,起源于 Reactive Extensions,是一个基于可观测数据流 Stream 结合观察者模式和迭代器模式的一种异步编程的应用库。**它的最大目的是提供一系列抽象的操作符可以对数据进行转换, 以满足以一种理想方式来管理事件序列所需要的一切

Rxjs 基本概念

Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。它能被多个observer订阅,每个订阅关系相互独立、互不影响
Observer (观察者):  观察者是由 Observable 发送的值的消费者,观察者只是一组回调函数的集合。
Subscription (订阅):  表示 Observable 的执行,主要用于取消 Observable 的执行
Subject(主体): 它是一个代理对象,既是一个 Observable 又是一个 Observer,它可以同时接受 Observable 发射出的数据,也可以向订阅了它的 observer 发射数据,同时,Subject 会对内部的 observers 清单进行多播(multicast)
Operators (操作符):  采用函数式编程风格的纯函数 (pure function),使用像 mapfilterconcatflatMap 等这样的操作符来处理集合。---RxJS 最伟大的发明

两个模式

观察者模式

在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常通过过呼叫各观察者所提供的方法来实现。 ---wiki

浏览器 DOM 事件的监听和触发应该是 Web 前端最典型的观察者模式的实现。

document.body.addEventListener('click', function listener(e) { console.log(e); });    
document.body.click(); // 模拟用户点击// 模拟用户点击

898684-20200804150423224-1837945848.png

迭代器模式

迭代器(Iterator)模式又叫游标(Sursor)模式,在面向对象编程里,迭代器模式是一种设计模式,是一种最简单也最常见的设计模式。迭代器模式可以把迭代的过程从从业务逻辑中分离出来,它可以让用户透过特定的接口巡访容器中的每一个元素而不用了解底层的实现

eth9k2ioqa.png

const iterable = [1, 2, 3]; 
const iterator = iterable[Symbol.iterator](); 
iterator.next(); // => { value: "1", done: false} 
iterator.next(); // => { value: "2", done: false} 
iterator.next(); // => { value: "3", done: false} 
iterator.next(); // => { value: undefined, done: true}

Observable 与 Observer

一行最经典的代码

observable.subscribe(observer);

顾名思义,Observable就是“可以被观察的对象”即“可被观察者”,而Observer就是“观察者”,连接两者的桥梁就是Observable对象的函数 subscribe

Observable (可观察对象)

Observables 是使用 Rx.Observable.create 或创建操作符创建的,并使用观察者来订阅它,然后执行它并发送 next / error / complete 通知给观察者,而且执行可能会被清理。这四个方面全部编码在 Observables 实例中,但某些方面是与其他类型相关的,像 Observer (观察者) 和 Subscription (订阅)

创建 Observables

const Rx = require('rxjs/Rx') 
const myObservable = Rx.Observable.create(observer => {

  console.log('observable start work')
  
  observer.next('1'); 
  setTimeout(() => observer.next('bar'), 1000); 
});

Observables 可以使用 create 来创建, 但通常我们使用所谓的创建操作符, 像 offrominterval、等等。

Observable 可传递三种类型的值

  • "Next" 通知: 发送一个值,比如数字、字符串、对象,等等。
  • "Error" 通知: 发送一个 JavaScript 错误 或 异常。
  • "Complete" 通知: 不再发送任何值。

"Next" 通知是最重要,也是最常见的类型:它们表示传递给观察者的实际数据。"Error" 和 "Complete" 通知可能只会在 Observable 执行期间发生一次,并且只会执行其中的一个

在 Observable 执行中, 可能会发送零个到无穷多个 "Next" 通知。如果发送的是 "Error" 或 "Complete" 通知的话,那么之后不会再发送任何通知了。

Observer (观察者)

什么是观察者?  - 观察者是由 Observable 发送的值的消费者。观察者只是一组回调函数的集合,每个回调函数对应一种 Observable 发送的通知类型:nexterror 和 complete 。
下面的示例是一个典型的观察者对象:

const observer = { 
  next: function(value) { console.log(value); }, 
  error: function(error) { console.log(error) }, 
  complete: function() { console.log('complete')}
}

在上面的代码实例中,Observer对象都是一个对象,可以包含next、complete和error三个方法,用于接受Observable的三种不同事件,如果我们根本不关心某种事件的话,也可以不实现对应的方法。比如对于一个永远不会结束的Observable,真的没有必要提供complete方法,因为它永远不会被调用到

比如我们这样去定义observer 也是可以正常运行的

const observer = { 
  next: function(value) { console.log(value); }, 
  error: function(error) { console.log(error) } 
}

observable三种事件 与observer 三个方法对应关系:

Angular-Observable-Tutorial-how-observable-and-observers-communicates-with-call-backs-.jpeg

订阅 Observables

const myObservable = Rx.Observable.create((observer) => { 
  console.log('observable start working')
  observer.next('111') 
  setTimeout(() => { observer.next('777') }, 3000)
}) 
const observer = { 
  next: function(value) { console.log(value); }, 
}
myObservable.subscribe((value) => console.log(value));
// myObservable.subscribe(observer)

这里直接使用subscribe方法让一个observer订阅一个Observable

订阅 Observable 像是调用函数, 并提供接收数据的回调函数。
这与像 addEventListener / removeEventListener 这样的事件处理方法 API 是完全不同的。使用 observable.subscribe,在 Observable 中不会将给定的观察者注册为监听器。Observable 甚至不会去维护一个附加的观察者列表。

subscribe 调用是启动 “Observable 执行”的一种简单方式, 并将值或事件传递给本次执行的观察者。

Observable.create(function subscribe(observer) {...}) 中...的代码表示 “Observable 执行”,它是惰性运算,只有在每个观察者订阅即subscribe函数调用后才会执行。随着时间的推移,执行会以同步或异步的方式产生多个值。上述代码片段在subscribe函数调用之前不会有任何的打印

清理 Observable 执行 / 取消订阅

现在已经了解了Observable和Observer之间如何建立关系,两者之间除了要通过subscribe建立关系,有时候还需要断开两者的关系,例如,Observer只需要监听一个Observable对象三秒钟时间,三秒钟之后就不关心这个Observable对象的任何事件了,这时候怎么办呢?

这就涉及一个“退订”(unsubscribe)的概念。

当调用了 observable.subscribe ,观察者会被附加到新创建的 Observable 执行中。这个调用还返回一个对象,即 Subscription (订阅):

const myObservable = Rx.Observable.create(observer => { 
  observer.next('foo'); 
  setTimeout(() => observer.next('bar'), 1000); 
}); 
const subscription = myObservable.subscribe(x => console.log(x)); 
// Observable 执行是通过使用观察者调用 subscribe 方法启动的 
// 这会取消正在进行中的 Observable 执行 
subscription.unsubscribe();
// 'bar' 将不会被打印

执行示意图 1_DGoQrWmardD-RQA7E_2OZg.jpeg

观察者+迭代器模式

结合迭代器 Iterator 来理解Observer的三个方法:

  • next()Observer通过提供 next 方法来接受Observable流(集合),是一种 push 形式(推送)。

    • 对比 Iterator,则是通过调用 iterator.next() 拿值,是一种 pull 的形式(拉取)。
  • complete() :当不再有新的值发出时,将触发 Observercomplete 方法。

    • 对比 Iterator ,则是在 next() 的返回结果中的 donetrue 时,则表示 complete
  • error() :当处理事件中出现异常时,通过try-catch捕获异常,Observer 提供 error 方法来接收错误进行统一处理。

一句话概述 RxJS 中实现的观察者+迭代器模式:就是将观察者Observer注入到可观察对象Observable中,然后在可观察对象Observable中通过调用观察者Observer提供的 nextcompleteerror 方法处理流的不同状态,以实现对数据流的一种顺序访问处理。

Subject

它是一个代理对象,既是一个 Observable 又是一个 Observer,它可以同时接受 Observable 发射出的数据,也可以向订阅了它的 observer 发射数据,同时,Subject 会对内部的 observers 清单进行多播(multicast)

1_sMOivM3uFVgBHJO_2JozKg.png

因为 Subject 是观察者,这也就在意味着你可以把 Subject 作为参数传给任何 Observable 的 subscribe 方法,如下面的示例所展示的:

const mySubject = new Rx.Subject();

mySubject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
mySubject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

const myObservable = Rx.Observable.from([1, 2]);

myObservable.subscribe(mySubject); // mySubject 订阅了myObservable

执行结果:

observerA: 1
observerB: 1
observerA: 2
observerB: 2

使用上面的方法,我们基本上只是通过 Subject 将单播的 Observable 执行转换为多播的。这也说明了 Subjects 是将任意 Observable 执行共享给多个观察者的唯一方式。

olzy0vdir8.png

单播

普通的Observable 是单播的,那么什么是单播呢?

单播的意思是,每个普通的 Observables 实例都只能被一个观察者订阅,当它被其他观察者订阅的时候会产生一个新的实例。也就是普通 Observables 被不同的观察者订阅的时候,会有多个实例,不管观察者是从何时开始订阅,每个实例都是从头开始把值发给对应的观察者。

RxJS是支持一个Observable被多次subscribe的,所以,RxJS支持多播,但是,表面上看到的是多播,实质上还是单播。下面是一个表面上看起来是“多播”的代码例子:

import {Observable} from 'rxjs/Observable'; 
import 'rxjs/add/observable/interval'; 
import 'rxjs/add/operator/take';

const tick$ = Observable.interval(1000).take(3); 
tick$.subscribe(value => console.log('observer 1: ' + value)); 
setTimeout(() => { 
  tick$.subscribe(value => console.log('observer 2: ' + value)); 
}, 1500);

//输出
observer 1: 0 
observer 1: 1 
observer 2: 0 
observer 1: 2 
observer 2: 1 
observer 2: 2

多播

实现多播能力就是实现我们不论什么时候订阅只会接收到实时的数据的功能。

const tick$ = Observable.interval(1000).take(3); 
const mySubject = new Rx.Subject();
mySubject.subscribe(value => console.log('observer 1: ' + value)); 
setTimeout(() => { 
  mySubject.subscribe(value => console.log('observer 2: ' + value)); 
}, 1500);
tick$.subscribe(mySubject)

//输出 observer2 不再接收 ‘0’
observer 1: 0 
observer 1: 1 
observer 2: 1
observer 1: 2 
observer 2: 2

每个 Subject 都是观察者。  - Subject 是一个有如下方法的对象: next(v)error(e) 和 complete() 。要给 Subjetc 提供新值,只要调用 next(theValue),它会将值多播给已注册监听该 Subject 的观察者们。

 const mySubject = new Rx.Subject(); 
 mySubject.subscribe(value => console.log('observer: ' + value));
 mySubject.next('bar')
 
// 输出  
bar

1_MUQR32wx9OqZpq3Pp2V76A.webp

操作符

尽管 RxJS 的根基是 Observable,但最有用的还是它的操作符。操作符是允许复杂的异步代码以声明式的方式进行轻松组合的基础代码单元。

操作符是 Observable 类型上的方法,比如 .map(...).filter(...).merge(...),等等.操作符是函数,它基于当前的 Observable 创建一个新的 Observable。这是一个无副作用的操作:前面的 Observable 保持不变。

The-RxJS-Operator-transforms-the-source-into-a-new-observable.webp

操作符类型

根据功能,操作符可以分为以下类别:
❑ 创建类(creation)
❑ 转化类(transformation)
❑ 过滤类(filtering)
❑ 合并类(combination)
❑ 多播类(multicasting)
❑ 错误处理类(error Handling)
❑ 辅助工具类(utility)
❑ 条件分支类(conditional & boolean)
❑ 数学和合计类(mathmatical & aggregate)

5BLZJz0.png 创建型Operators

对于任何数据的处理或使用来说,我们首先会去关注的莫过于,它从哪里来,如何产生的,以及我们该怎么获取。

interval

interval.png 默认从0开始,这里设定的时间为1s一次,它会持续不断的按照指定间隔发出数据,在我们需要获取一段连续的数字时,或者需要定时做一些操作时都可以使用该操作符

fromEvent

fromEvent.png

创建一个 Observable,该 Observable 发出来自给定事件对象的指定类型事件。可用于浏览器环境中的Dom事件或Node环境中的EventEmitter事件等。

转换型操作符

map

map.pngjs中数组的map方法较多的话,可能这里基本就不用看了,用法完全一致。

merge
创建一个输出 Observable ,它可以同时发出每个给定的输入 Observable 中值,通过把多个 Observables 的值混合到一个 Observable 中来将其打平。

merge.png

combineLatest
当任意 observable 发出值时,发出每个 observable 的最新值。当有多个长期活动的 observables 且它们依靠彼此来进行一些计算或决定时,此操作符是最适合的

combineLatest.png distinctUntilChanged

只有当当前值与之前最后一个值不同时才将其发出

截屏2022-11-29 22.56.20.png

switchMap

将每个源值投射成 Observable,该 Observable 会合并到输出 Observable 中, 并且只发出最新投射的 Observable 中的值

switchMap.png

暂时先举这几个例子详见RxJS操作符操作符弹珠图

rxjs 优点

特点

  • 代码量的大幅度减少
  • 代码可读性的提高
  • 很好的处理异步
  • 事件管理、调度引擎
  • 十分丰富的操作符
  • 声明式的编程风格

总体来说,对于RxJS这种数据流形式来处理我们日常业务中错综复杂的数据是十分有利于维护的,并且在很多复杂的数据运算上来说,RxJS能够给我们带来许多提高效率的操作符,同时还给我们带来了一种新颖的数据操作理念。

我们可以将RxJS比喻做可以发射事件的一种lodash库,封装了很多复杂的操作逻辑,让我们在使用过程中能够以更优雅的方式来进行数据转换与操作。

参考资料

RxJS 官方文档
Reactive Extension
RxJS 操作符弹珠图
RxJS在线编程