如何理解 RxJS?

时间:2021-11-17 01:27:23

在 Angular 2 中,我们遇到了一个新的概念 —— RxJS。

对很多人而言,这可能是一个比较难以理解的地方。所谓的难以理解并不是说 API 有多复杂,而是对于 RxJS 本身的理念就无从下手。

所以,这里简单地对 RxJS 进行一些介绍。

一、函数响应式编程(FRP)

FRP 早在上世纪 90 年代就已经被提出,但由于早期的编译器和运行时能力有限,大部分编程实践中往往采用的是人迁就机器的理念,即命令式编程,或者叫广义的面向过程。(这里指包括面向对象在内的以指定步骤的方式来编程的方式)

而另一类编程方式,叫做声明式编程。在声明式编程中,,并不会为一个操作指定步骤,而只是单纯的给出我们的意图,而函数响应式编程就是声明式编程中的一个重要实践。

其实,声明式编程本身并不特别,例如我们的 HTML 就是一个很常见的声明式编程。

比如我们有下面的 HTML:

<ul class="list"> <li class="item">1</li> <li class="item">2</li> </ul>

对应到命令式编程中,我们大概会得到下面的代码:

const ul = document.createElement(‘ul‘) ul.className = ‘list‘ const lis = [1, 2].map(n => { li = document.createElement(‘li‘) li.className = ‘item‘ li.textContent = n.toString() }) lis.forEach(li => ul.appendChild(li))

通过比较,我们可以很直观的发现在一些特定场景中声明式编程会比命令式编程要简洁很多。

除了 HTML 之外,XML、CSS、SQL 和 Prolog 等也都是声明式编程语言。而对于其它的通用编程语言而言,虽然语言本身大多属于命令式语言,但在特定场景下依然可以使用声明式编程的实践。

二、Reactive Extensions

巨硬大法好!

Reactive Extensions 是微软(研究院)提出的一个函数响应式编程抽象,最早用于 .Net 中(位于 System.Reactive 命名空间下但不在标准库中),之后也被大量移植到其它语言,比如我们这里到 RxJS 就是 Rx 的 JavaScript 移植版本。

虽然语言不同,但 Rx 的核心思想以及主要 API 仍然是通用的,所以我们这里的内容同样适用于 RxWhatever。

为了解释函数响应式,我们先来简单看一看函数式编程。例如在 lodash 中,我们可以使用方法链的方式来实现复杂操作:

interface Person {name: string, age: number} declare const people: Person[] _(people) .filter(person => person.age >= 18) .forEach(person => console.log(`${person.name} is an adult.`))

对于一般的函数式编程而言,我们会对数据的某种集合容器(Array、Map、Set 等等)进行组合与变换,而在 Rx 中,我们处理的是一类特殊的数据集合叫做 Observable,可以看作一个事件流。

现在,由于每一个数据项都是一个事件,我们并不能在一开始就获得所有的事件,并且事件具体在什么时候产生也无从得知。

一个很简单的例子,我们仍然需要处理上面的 person,但 person 不是同时获取的,而是通过用户交互动态创建的。当然,我们也可以把所有操作都写在回调函数里,但那样会造成极高的耦合度,破坏代码质量。

所以,我们可以把这里 person 的创建做成一个事件流,并不去关心在什么地方会用到,以及怎样用到。

class SomeComponent { people: Subject<Person> onCreate(person: Person) { people.next(person) } }

而对于真正的调用方,只要获取对应的 Observable,然后进行后续操作。

const people$ = getPeopleObservableSomehow() people$.filter(person => person.age >= 18) .subscribe(person => console.log(`${person.name} is an adult.`))

如果我们还有其它的数据源,比如初始数据从服务端获取,之后的数据才通过交互产生,我们可以进行一次组合:

const initialPeople = getPeopleSomehow() const people$ = getPeopleObservableSomehow() const allPeople$ = Observable.from(initialPeople) .mergeMap(people$) allPeople$.filter(person => person.age >= 18) .subscribe(person => console.log(`${person.name} is an adult.`))

这样,可以让我们的代码具有较高的复用性,同时又能极大地降低了耦合性,提高代码质量。