RxJS 介绍

RxJS(Reactive Extensions for JavaScript)是一个用于处理异步数据流的库,它基于观察者模式和迭代器模式,提供了强大的工具来处理事件、异步操作和数据流。RxJS 的核心概念是 Observable(可观察对象),它代表一个数据流,可以被订阅(subscribe)以接收数据。

官网介绍: 可以把 RxJS 当做是用来处理事件的 Lodash

基础概念

在 RxJS 中用来解决异步事件管理的的基本概念是:

  • Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
  • Observer (观察者): 一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
  • Subscription (订阅): 表示 Observable 的执行,主要用于取消 Observable 的执行。
  • Operators (操作符): 采用函数式编程风格的纯函数 (pure function),使用像 mapfilterconcatflatMap 等这样的操作符来处理集合。
  • Subject (主体): 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
  • Schedulers (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeoutrequestAnimationFrame 或其他。

Observable 剖析

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

Observable 的核心关注点:

  • 创建 Observables
  • 订阅 Observables
  • 执行 Observables
  • 清理 Observables

通常我们使用所谓的创建操作符, 像 offrominterval、等等

订阅 Observables

订阅 Observable 像是调用函数, 并提供接收数据的回调函数。可以传入一个函数或者是对象

tsx 复制代码
var observer = {
  next: x => console.log('Observer got a next value: ' + x),
  error: err => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
};

observable.subscribe(observer);
// or
observable.subscribe(x => console.log('Observer got a next value: ' + x));
// or next,error,complete
observable.subscribe(
  x => console.log('Observer got a next value: ' + x),
  err => console.error('Observer got an error: ' + err),
  () => console.log('Observer got a complete notification')
);

执行 Observables

Observable 执行可以传递三种类型的值:

  • "Next" 通知: 发送一个值,比如数字、字符串、对象,等等。
  • "Error" 通知: 发送一个 JavaScript 错误 或 异常。
  • "Complete" 通知: 不再发送任何值。
tsx 复制代码
var observable = Rx.Observable.create(function subscribe(observer) {
  try {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
  } catch (err) {
    observer.error(err); // 如果捕获到异常会发送一个错误
  }
});

var subscription = observable.subscribe(x => console.log(x));
// 删除
subscription.unsubscribe();

当你订阅了 Observable,你会得到一个 Subscription ,它表示进行中的执行。只要调用 unsubscribe() 方法就可以取消执行。

在内部提供 unsubscribe 函数

tsx 复制代码
var observable = Rx.Observable.create(function subscribe(observer) {
  // 追踪 interval 资源
  var intervalID = setInterval(() => {
    observer.next('hi');
  }, 1000);

  // 提供取消和清理 interval 资源的方法
  return function unsubscribe() {
    clearInterval(intervalID);
  };
});

Subscription (订阅)

Subscription 基本上只有一个 unsubscribe() 函数,这个函数用来释放资源或去取消 Observable 执行。

tsx 复制代码
var observable = Rx.Observable.interval(1000);
var subscription = observable.subscribe(x => console.log(x));
// 稍后:
// 这会取消正在进行中的 Observable 执行
// Observable 执行是通过使用观察者调用 subscribe 方法启动的
subscription.unsubscribe();

Subscription 还可以合在一起,这样一个 Subscription 调用 unsubscribe() 方法,可能会有多个 Subscription 取消订阅 。

Subscriptions 还有一个 remove(otherSubscription) 方法,用来撤销一个已添加的子 Subscription

tsx 复制代码
var observable1 = Rx.Observable.interval(400);
var observable2 = Rx.Observable.interval(300);

var subscription = observable1.subscribe(x => console.log('first: ' + x));
var childSubscription = observable2.subscribe(x => console.log('second: ' + x));

subscription.add(childSubscription);

setTimeout(() => {
  // subscription 和 childSubscription 都会取消订阅
  subscription.unsubscribe();
}, 1000);

Subject (主体)

RxJS Subject 是一种特殊类型的 Observable,它允许将值多播给多个观察者,所以 Subject 是多播的,而普通的 Observables 是单播的(每个已订阅的观察者都拥有 Observable 的独立执行)。

每个 Subject 都是 Observable 。也可以每个 Subject 都是观察者。

观察者

tsx 复制代码
import { Subject } from "rxjs";

var subject = new Subject();

subject.subscribe({
  next: (v) => console.log("observerA: " + v),
});
subject.subscribe({
  next: (v) => console.log("observerB: " + v),
});

subject.next(1);
subject.next(2);

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

当做 Observable

tsx 复制代码
import { from, Subject } from "rxjs";

var subject = new Subject();

subject.subscribe({
  next: (v) => console.log("observerA: " + v),
});
subject.subscribe({
  next: (v) => console.log("observerB: " + v),
});

var observable = from([1, 2, 3]);

observable.subscribe(subject); // 你可以提供一个 Subject 进行订阅

// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3

还有一些特殊类型的 SubjectBehaviorSubjectReplaySubjectAsyncSubject

BehaviorSubject

Subject 的其中一个变体就是 BehaviorSubject,它有一个"当前值"的概念。它保存了发送给消费者的最新值。并且当有新的观察者订阅时,会立即从 BehaviorSubject 那接收到"当前值",多个订阅拿到的是最新的值,可以当做管道处理。

tsx 复制代码
import { BehaviorSubject } from "rxjs";
const subject = new BehaviorSubject(0); // 0 is the initial value

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`),
});

subject.next(1);
subject.next(2);

subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`),
});

subject.next(3);

// Logs
// observerA: 0
// observerA: 1
// observerA: 2

// observerB: 2
// observerA: 3
// observerB: 3

ReplaySubject

ReplaySubject 类似于 BehaviorSubject,发送旧值给新的订阅者,但它还可以记录 Observable 执行的一部分。

tsx 复制代码
import { ReplaySubject } from "rxjs";
// 缓存3个值在缓冲区,有新的订阅就发送,最新的3个
const subject = new ReplaySubject(3); // buffer 3 values for new subscribers

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`),
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`),
});

subject.next(5);

// Logs:
// observerA: 1
// observerA: 2
// observerA: 3
// observerA: 4
// observerB: 2
// observerB: 3
// observerB: 4
// observerA: 5
// observerB: 5

AsyncSubject

AsyncSubject 是另一个 Subject 变体,只有当 Observable 执行完成时 (执行 complete()),它才会将执行的最后一个值发送给观察者。

tsx 复制代码
import { AsyncSubject } from "rxjs";
const subject = new AsyncSubject();

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`),
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`),
});

subject.next(5);
subject.complete();

// Logs:
// observerA: 5
// observerB: 5

Operators (操作符)

操作符是 Observable 类型上的方法 ,比如 .map(...).filter(...).merge(...),等等。当操作符被调用时,它们不会改变 已经存在的 Observable 实例。相反,它们返回一个新的 Observable ,它的 subscription 逻辑基于第一个 Observable 。

操作符是函数,它基于当前的 Observable 创建一个新的 Observable。这是一个无副作用的操作:前面的 Observable 保持不变。

tsx 复制代码
import { of, map, first } from 'rxjs';

of(1, 2, 3)
  .pipe(map((x) => x * x))
  .subscribe((v) => console.log(`value: ${v}`));

// Logs:
// value: 1
// value: 4
// value: 9

// first 操作符
of(1, 2, 3)
  .pipe(first())
  .subscribe((v) => console.log(`value: ${v}`));

// Logs:
// value: 1

Piping 管道

管道操作符是函数,因此可以像普通函数一样使用

tsx 复制代码
obs.pipe(op1(), op2(), op3(), op4());

常用操作符

map、filter、mergeMap、of、zip、first ...

Scheduler 调度器

调度器控制着何时启动 subscription 和何时发送通知。它由三部分组成:

  • 调度器是一种数据结构。 它知道如何根据优先级或其他标准来存储任务和将任务进行排序。
  • 调度器是执行上下文。 它表示在何时何地执行任务(举例来说,立即的,或另一种回调函数机制(比如 setTimeout 或 process.nextTick),或动画帧)。
  • 调度器有一个(虚拟的)时钟。 调度器功能通过它的 getter 方法 now() 提供了"时间"的概念。在具体调度器上安排的任务将严格遵循该时钟所表示的时间。

把任务全部转换为异步

tsx 复制代码
import { Observable, observeOn, asyncScheduler } from "rxjs";

const observable = new Observable((observer) => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
}).pipe(observeOn(asyncScheduler));

console.log("just before subscribe");
observable.subscribe({
  next(x) {
    console.log("got value " + x);
  },
  error(err) {
    console.error("something wrong occurred: " + err);
  },
  complete() {
    console.log("done");
  },
});
console.log("just after subscribe");
// just before subscribe
// just after subscribe
// got value 1
// got value 2
// got value 3
// done

observeOn(asyncScheduler)new Observable 和最终观察者之间引入了一个代理观察者。

proxyObserver 创建于 observeOn(asyncScheduler) ,其 next(val) 功能大致如下:

tsx 复制代码
const proxyObserver = {
  next(val) {
    asyncScheduler.schedule(
      (x) => finalObserver.next(x),
      0 /* delay */,
      val /* 第一个参数的函数实参值 */
    );
  },

  // ...
};

async 调度器操作符使用了 setTimeoutsetInterval,即使给定的延迟时间为0。照例,在 JavaScript 中,我们已知的是 setTimeout(fn, 0) 会在下一次事件循环迭代的最开始运行 fn

Scheduler Types 调度程序类型

Scheduler ****调度程序 Purpose ****目的
null 不传递任何调度器的话,会以同步递归的方式发送通知。用于定时操作或尾递归操作。
queueScheduler 当前事件帧中的队列调度。用于迭代操作。
asapScheduler 微任务的队列调度,它使用可用的最快速的传输机制,比如 Node.js 的 process.nextTick() 或 Web Worker 的 MessageChannel 或 setTimeout 或其他。用于异步转换。
asyncScheduler 使用 setInterval 的调度。用于基于时间的操作符。
animationFrameScheduler 安排在下次浏览器内容重绘之前执行的任务。可用于创建流畅的浏览器动画。

使用调度器

静态创建操作符通常可以接收调度器作为参数。 举例来说,from(array, scheduler) 可以让你指定调度器,当发送从 array 转换的每个通知的时候使用。调度器通常作为操作符的最后一个参数。下面的静态创建操作符接收调度器参数:

  • bindCallback
  • bindNodeCallback
  • combineLatest
  • concat
  • empty
  • from
  • fromPromise
  • interval
  • merge
  • of
  • range
  • throw
  • timer

使用 subscribeOn 来安排在什么上下文中 subscribe() 调用会发生。默认情况下,在 Observable 上的 subscribe() 调用会同步且立即发生。然而,您可以使用实例操作符 subscribeOn(scheduler) 来延迟或安排实际订阅在给定的调度器上发生,其中 scheduler 是您提供的参数。

示例

  1. 简单的事件注册和监听
tsx 复制代码
var button = document.querySelector('button');
button.addEventListener('click', () => console.log('Clicked!'));

// rxjs 的写法
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
  .subscribe(() => console.log('Clicked!'));
  1. 发送多个值,包含同步和异步
tsx 复制代码
import { Observable } from "rxjs";

const observable = new Observable((subscriber) => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {
    subscriber.next(4);
    subscriber.complete();
  }, 1000);
});

observable.subscribe({
  next: (value) => console.log(value),
  complete: () => console.log("Complete"),
  error: (err) => console.error(err),
});


// 1
// 2
// 3
// 等待 1000ms
// 4
// Complete

Observer

Observer 是一个包含 nexterrorcomplete 方法的对象,用于处理 Observable 发出的值、错误和完成通知。

Subscription

Subscription 表示 Observable 的执行,可以通过 unsubscribe 方法来取消订阅。

  1. 使用操作符处理
tsx 复制代码
import { of } from "rxjs";
import { map, filter } from "rxjs/operators";

const numbers = of(1, 2, 3, 4, 5);

const squaredNumbers = numbers.pipe(
  filter((x) => x % 2 === 0),
  map((x) => x * x)
);

squaredNumbers.subscribe((x) => console.log(x));

先试用 of 创建一个 Observable,然后使用 pipe 管道处理值。

相关推荐
LYFlied4 分钟前
【每日算法】LeetCode 84. 柱状图中最大的矩形
前端·算法·leetcode·面试·职场和发展
Bigger6 分钟前
Tauri(21)——窗口缩放后的”失焦惊魂”,游戏控制权丢失了
前端·macos·app
Bigger25 分钟前
Tauri (20)——为什么 NSPanel 窗口不能用官方 API 全屏?
前端·macos·app
bug总结26 分钟前
前端开发中为什么要使用 URL().origin 提取接口根地址
开发语言·前端·javascript·vue.js·html
一招定胜负1 小时前
网络爬虫(第三部)
前端·javascript·爬虫
Shaneyxs2 小时前
从 0 到 1 实现CloudBase云开发 + 低代码全栈开发活动管理小程序(13)
前端
半山烟雨半山青2 小时前
微信内容emoji表情包编辑器 + vue3 + ts + WrchatEmogi Editor
前端·javascript·vue.js
码途潇潇2 小时前
Vue 事件机制全面解析:原生事件、自定义事件与 DOM 冒泡完全讲透
前端·javascript
zmirror2 小时前
Monorepo 在 Docker 中的构建方案方案
前端
用户47949283569152 小时前
node_modules 太胖?用 Node.js 原生功能给依赖做一次大扫除
前端·后端·node.js