Rx.js学习所得

前言

学习Rx.js也差不多3个来月了(准确来讲,应该是第一次学习失败,隔了一个月第二次总算是有点理解它的思想了)。不得不说,这玩意虽然挺有意思的,但真滴难。其体系是有点庞杂的,操作符是真滴多(还要吐槽文档也不行。。。),但总的来说,虽然还没用上Rx.js,但就目前学习它的所得,倒是让我收获颇丰。所以写篇文章总结一下自己的体会还是很有必要的,后面也需要写不少文章来记录操作符的一些学习所得,以备忘记(记性不好的是真滴难受)。

一、学习动机

总的来说,第一次学习Rx.js是由于看了篇文章,感觉这种东西是有点厉害,加上当时学习redux,同样都体现着函数式编程的思想。因此希望可以从Rx.js中对函数式编程上有所收获,所以就学了一下。结果一看文档,那面屏幕的操作符看的我一脸懵逼,而且也苦苦不能理解它的抽象思想,结果学了1个星期就放弃了。第二次学习则是在看了下redux-saga的文档后,自我感觉不咋喜欢它的语法,感觉还是async/await要优雅些,觉得做个写代码的要求有点追求,经过了解后知道了有个东西叫redux-observable,看了一下代码示例,纯粹的对象字面量的actions,有点厉害的,就打算学一学看看。仔细一看是基于rx.js的,所以弄到头还是得硬上Rx.js。不过这次倒是坚持下来了,虽然还没上手实践它,但总算是理解了它的思想,并通过学习它对很多以往虽然有学习过,但体会不深的东西也有了更为深刻的理解。

总的来说,有以下收获:

  • 理解函数式编程的思想
  • 理解响应式编程的思想
  • 抽象的能力提升
  • Rx.js

二、函数式编程的思想

现在回想起来,第一次之所以没有很好地理解Rx.js,其中很大的原因是对函数式编程的思想理解的不够(当然现在还是不够),虽然当时学习了Redux,也在用它实践,但对其的理解还是不够透彻。而第二次学习则算是理解了Redux的一些思想(纯函数,数据的不可变)。因此在第二次学习Rx.js的时候,要轻松不少,而且对函数式编程的思想也有了更为深刻的理解。

所谓纯函数,其实就是要满足以下两个条件:

  1. 函数的执行结果是由输入参数所决定的,不受除参数之外的任何数据的影响
  2. 函数中不会修改任何外部的状态。

对于这两方面可以看一个示例:

1
2
3
4
5
6
7
const arr = [1, 2, 3, 4]
const addVal = (arr, newVal) => {
arr.push(newVal)
return arr
}
addVal(arr, 6)
console.log(arr) // [1, 2, 3, 4, 6]

就比如上面的代码,add函数在内部直接调用push函数,将新元素直接添加到数组的尾部,这样就会直接改变数组的值。这样看起来好像并没有什么副作用,但仔细想想,假如这是一份数据,当我们将数据传入这个函数的时候,将原数据进行直接修改,这很容易导致其他引用此数据的函数出现意料不到的问题。就比如下面这个:

1
2
3
4
5
6
7
8
const arr = [1,2,3,4]
const addOne = (arr) => arr.map(newVal => newVal + 1)
const addVal = (arr, newVal) => {
arr.push(newVal)
return arr
}
addVal(arr, 6)
addOne(arr)

可以看到上述代码,我们在addOne(arr)所期望的结果是原本的arr数组内的所有元素都加上1,但实际结果却是:

1
[2, 3, 4, 5, 7]

所产生的结果与我们所预料的完全不同。也正是出于这番原因,我们在使用redux的时候,在reducer中,也作出要求:数据是不可变的,就是为了避免诸如此类的事情的发生。出于这番考虑,我们就可以通过对上述代码进行修改,以满足我们的需求:

1
2
3
4
5
6
7
const arr = [1,2,3,4]
const addOne = (arr) => arr.map(newVal => newVal + 1)
const addVal = (arr, newVal) => {
return [...arr, newVal]
}
addVal(arr, 6)
addOne(arr)

现在再去执行,就可以发现,我们所得的结果与我们所期望的是一致的:

1
[2, 3, 4, 5]

因此总计一下我现阶段所理解的函数式编程:将一个个操作数据的方法用函数进行封装,并且要满足以下的条件:

  • 这个函数是一个纯函数。
  • 数据是不可变的,因此返回时返回一个新的数据,而不是在原有数据上进行修改。

也正是出于以上的这些考虑,在Rx.js中,我们每个Observable对象都会返回一个Observable对象:

1
2
3
4
const arr = [1,2,3,4]
const result$ = arr$.filter(x => x % 2 === 0).map(x => x * 2)
result$.subscribe(console.log)
// 4 8

之所以Rx.js可以完成上述的这种操作其实就是由于filter和map都是Observable对象且它会返回一个新的Observable对象,不会对原有的Observable对象进行修改。

三、响应式编程的思想

对于响应式编程,我感觉最近几年用的很广。别的不说,Vue就是其中的代表,另外还有mobx也是其中一员。以我现在的理解就是这样:本质上有点观察者模式的意思。将数据的产生与数据的处理逻辑相分离,在观察者observer方面无需关系数据的来源,只管负责处理数据的逻辑的实现,而被观察者Observable则只要关系产生数据流,并通知所有注册的观察者,无需担心这些数据是如何被处理的。

有个例子提的很好,就像EXCEL的表格,每个格子的输入我们都可以将其看做一个数据流,用户在哪个格子输入是我们事先无法预料的,但我们也无需关心这些,我们只要将这些输入一视同仁的看做数据流,只要数值发生改变,我们就进行相应的处理。

在我的理解中,其实光有响应式编程其实还是有所不足的,如果我们只是单纯的这些事件抽象为数据流,并对这个数据流进行处理,不做规范的话,可能会出现数据状态不好管理的情况,就比如我上面举的那个函数式编程的例子。我们对数据流进行操作后,如果直接改动数据流,很可能会导致一些我们无法预料的事情发生。因此,函数编程+响应式编程才能将两者的优点都吸收,这也就是Rx.js的魅力所在吧。

待续