🔥 rxjs极简入门避免晦涩的概念

157 阅读6分钟

前言

最近学习 nestjs,在 nestjs 可以使用 rxjs 处理异步操作和事件驱动的任务。于是开始进入了 rxjs 的学习过程。网上有很多过于 rxjs 的教程和讲解,但进去学习就会发现不是版本过时了(目前的版本是7.8.1),就是上来一顿晦涩的概念,打开网站学习也是一上来就是各种概念,让一脸懵逼。这使得 rxjs 的学习曲线也陡然攀升了。这里分享一下自己的踩坑和学习过程,避免哪些晦涩的概念。

晦涩概念汇总(建议忽略,深入原理时再学习)

  1. 官网文章上来就用各种设计模式(迭代器模式、发布订阅模式、函数式编程)来介绍 rxjs ,很难理解也很容易劝退。忽略这些设计模式的晦涩概念,不要总带着这些概念去理解 rxjs ,记住官网的这一句话即可: 可以把 RxJS 当做是用来处理事件的 [Lodash](https://lodash.com/) 。rxjs 就是一个工具库,仅是处理的对象是事件而已,准确的说是流而已。
  2. 官网中关于 rxjs 数据生产和消费的设计思路讲解也无需时刻带入到学习中,仅是针对 rxjs 在业务中的落地应用而已,可能就是处理一些异步的数据流,组合数据生产 Observable 和数据消费 Observer 而已,在业务应用是可先忽略这些概念,在需要深入理解时在回头带入具体的应用场景去理解反而透彻。
  3. 关于 Observable Observer Subscription Operators Subject Schedulers 名词概念的理解。这些名词很容易与发布订阅模式联系起来,但是请先忽略发布订阅模式,看示例落地在业务中的实践即可。

学习和实践方式

  1. 使用 Vite 按照个人熟悉的技术栈建一个示例项目安装 rxjs。如:
yarn create vite react-rxjs --template react-ts
  1. 使用弹珠图学习时序 rxviz image.png

rxjs 的介绍

官网开始针对 rxjs 的介绍就是 Think of RxJS as Lodash for events.RxJS 当做处理事件的 Lodash 工具库。这里也就明确说明了 RxJS 就是一个工具库,会提供很多工具函数针对事件进行处理,这里的事件可以理解为流。就像弹珠图展示的一样是随着时间的流逝数据会一个一个产生并推送出去。在 rxjs 就被称为 Observable ,简单理解就是会有一个数据集合,这个集合内有许多数据,而这些数据不是一次都输出,而是随着时间的流动,数据被不断的产生的。而这些流需要被处理,如流的转换、合并、截取等,而 rxjs 就提供这种处理流的能力,类似 Lodash 提供处理集合的能力一样。

Observable Observer Subscription 的理解

Observable Observer Subscriptionrxjs 首先需要理解的三个核心概念,但是文档内的说明比较晦涩这里我们白话讲一下。

  • Observable: represents the idea of an invokable collection of future values or events.
  • Observer: is a collection of callbacks that knows how to listen to values delivered by the Observable.
  • Subscription: represents the execution of an Observable, is primarily useful for cancelling the execution.
  • Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
  • Observer (观察者): 一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
  • Subscription (订阅): 表示 Observable 的执行,主要用于取消 Observable 的执行。

我们可以这么理解这几个核心概念:

  • Observable (可观察对象): 将其当成一个流更容易理解,他就是一个数据集合,在时间流逝中不断的输出,只是这个流被 subscribe 之后才会被输出,且不同的 subscribe 互相是独立。
  • Observer (观察者): 一个回调函数的集合,简单理解就是如何针对流中产生的数据进行处理。
  • Subscription (订阅): 表示 Observablesubscribe 之后的结果,用于取消 Observable 的执行也就是将流关闭。

如下的例子我们定义一个 Observable 流,每隔 1s 输出一个数字。

import { Observable } from 'rxjs'

const stream$ = new Observable<number>((subscriber) => {
  setTimeout(() => {
    subscriber.next(1)
  }, 1000);
  setTimeout(() => {
    subscriber.next(2)
  }, 2000);
  setTimeout(() => {
    subscriber.next(3)
  }, 3000);
  setTimeout(() => {
    subscriber.complete()
  }, 4000);
});
const subscription1 = stream$.subscribe({
  next: (value) => {
    console.log('observer1: ', value)
  },
  complete: () => {
    console.log('observer1 complete')
  }
})
const subscription2 = stream$.subscribe({
  next: (value) => {
    console.log('observer2: ', value)
  },
  complete: () => {
    console.log('observer2 complete')
  }
})

执行结果:

image.png

import { Observable } from 'rxjs'

const stream$ = new Observable<number>((subscriber) => {
  setTimeout(() => {
    subscriber.next(1)
  }, 1000);
  setTimeout(() => {
    subscriber.next(2)
  }, 2000);
  setTimeout(() => {
    subscriber.next(3)
  }, 3000);
  setTimeout(() => {
    subscriber.complete()
  }, 4000);
});
const subscription1 = stream$.subscribe({
  next: (value) => {
    console.log('observer1: ', value)
  },
  complete: () => {
    console.log('observer1 complete')
  }
})
const subscription2 = stream$.subscribe({
  next: (value) => {
    console.log('observer2: ', value)
  },
  complete: () => {
    console.log('observer2 complete')
  }
})

setTimeout(() => {
  subscription2.unsubscribe()
}, 1000)

执行结果:

image.png

这里我们思考几个问题:

  1. 同一个流是否可以被多次订阅,相互之间的订阅是否会有影响;
  2. 同一个流被多次订阅之后是否可以关闭,关闭是将这个流关闭,还是仅关闭单一的订阅

由上述两个例子我们可以看到 new Observable 仅是创建一个流 stream$,且未被订阅 .subscribe 之前什么数据也不会产生,类似 Promise 状态没有被 resolvereject 之间什么也不做就是 pending 在这里。而 .subscribe 就是启动这个流,只有启动了这个流才会不停的产生数据,这个被启动的流就是 Subscription,而 .subscribe 启动这个流需要传入的 Observer 针对流产生的数据进行处理,这里的处理就是 console.log。同时通过输出的结果可以看出来同一个流 stream$ 可以被多次订阅,且不同的订阅互相是独立的,从输出可以看出同一个流被订阅多次之后会输出相同的内容就可以看出来。且启动的流 subscription2unsubscribe 之后并不影响 subscription1 这个启动的流数据输出。也就是说 Observable 创建流,.subscribe 启动流,启动之后的流就是 Subscription,启动流需要传入 Observer

因此 Observable Observer Subscription 这三个名词可以简单理解为 Observable 定义了要产生一个什么样的数据集合,其可以被 subscribesubscribe 接收一个 Observer(定义如何处理接收到的数据),该方法的返回值是 Subscription, 存储了这个已经开启的流,Subscribe 具有 unscbscribe 方法,可以将这个流停止。仅停止当前 Subscription 而不影响其它被开启的流。

操作符

操作符是针对流进行处理的工具函数,简单理解就是针对流进行组合,由前一个流产生一个新的流。

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

操作符使得 rxjs 功能十分强大,也是在业务开发中被运用的能力。这里不需要了解每一个操作符的功能和能力,仅需要在开发需要应用时在查文档去了解即可。

这里举一个例子:

import { interval, take } from 'rxjs'

interval(1000).pipe(
  take(3)
).subscribe({
  next: (value) => {
    console.log(value)
  },
  complete: () => console.log('complete'),
})

输出结果:

image.png

Subject

Subject 也是一种 Observable ,可以简单的理解 SubjectEventEmitters 。可以将 Observable 产生的数据同时推送给多个 Observer ,业务开发中可以用来作为数据中线使用。

// eventBus.ts
import { Subject } from 'rxjs'

export const eventBus$ = new Subject<string>()

// componentA.tsx
import { eventBus$ } from './eventBus'

eventBus$.subscribe((value) => {
  console.log('我订阅了全局数据总线: ', value)
})

// componentB.tsx
import { eventBus$ } from './eventBus'

eventBus$.subscribe((value) => {
  console.log('我也订阅了全局数据总线: ', value)
})

// componentC.tsx
import { eventBus$ } from './eventBus'

eventBus$.next('全局数据总线')

输出结果:

image.png

Scheduler

Scheduler 主要是为了控制 Observable 何时发出数据以及发出数据的顺序。在实际开发中使用较少,可在场景需要时在深入。

总结

rxjs 需要学习的内容有很多,这里仅是个人学习时觉得可以简单快速上手投入业务的学习过程。如果有针对 rxjs 有学习和实践优秀的文章和视频欢迎文章下留言。