RxJS不彻底指北(入门篇)

什么是RxJS?

RxJS是一个JavaScript库,用来编写异步基于事件的程序。RxJS结合了观察者模式迭代器模式使用集合的函数式编程,以知足以一种理想方式来管理事件序列所须要的一切。前端

能够把RxJS看成用来处理事件的 Lodash

为何要学Rxjs?

在如今的Web开发中,异步(Async)操做随处可见,好比使用ajax提交一个表单数据,咱们须要等待服务端返回提交结果后执行后续操做,这就是一个典型的异步操做。虽然JavaScript为了方便开发者进行异步操做,提出了不少解决方案(callback,Promise,Async/await等等),可是随着需求越发复杂,如何优雅的管理异步操做仍然是个难题。vue

此外,异步操做API千奇百怪,五花八门:ajax

  1. DOM Events
  2. XMLHttpRequest
  3. fetch
  4. WebSockets
  5. Service Worker
  6. Timer
  7. ......

以上这些经常使用的API所有都是异步的,可是每一个使用起来却彻底不一样,无形中给开发者增长了很大的学习和记忆成本。npm

使用RxJS能够很好的帮助咱们解决上面两个问题,控制大量异步代码的复杂度,保持代码可读性,并统一API。编程

举个栗子:页面上有一个搜索框,用户能够输入文本进行搜索,搜索时要向服务端发送异步请求,为了减少服务端压力,前端须要控制请求频率,1秒最多发送5次请求,而且输入为空时不发送请求,最后将搜索的结果显示在页面上。segmentfault

一般咱们的作法是这样的,先判断输入是否为空,若是不为空,则构造一个截流函数来控制请求频率,这其中涉及到建立和销毁定时器,此外,因为每一个请求返回时间不肯定,如何获取最后一次搜索结果,须要构造一个栈来保存请求顺序, 想完美实现需求并不简单。设计模式

RxJS是如何解决这个问题的呢?请看下面的代码:数组

// 1.获取dom元素
const typingInput = document.querySelector("#typing-input"); // 输入
const typingBack = document.querySelector("#typing-back"); // 输出

// 2.模拟异步请求
const getData = value =>
  new Promise(resolve =>
    setTimeout(() => resolve(`async data ${value}`), 1000)
  );

// 3.RxJS操做
const source$ = fromEvent(typingInput, "input") // 建立事件数据流
.pipe( // 管道式操做
  map(e => e.target.value), // 获取输入的数据
  filter(i => i), // 过滤空数据
  debounceTime(200), // 控制频率
  switchMap(getData) // 转化数据为请求
);
// 4.输入结果
source$.subscribe(asyncData => (typingBack.innerHTML = asyncData));

这就是所有代码,也许有些地方看不太懂 ,不要紧,先不要着急,咱们分步解读一下。网络

  1. 使用选择器获取了两个dom元素,第一个是输入框,第二个是搜索结果的容器;
  2. 使用Promise来模拟一个异步请求的函数,1秒后返回请求结果;
  3. 这部分是RxJS操做,这里咱们要先介绍一个概念,“数据流”(stream,简称“流”),“流”是RxJS中一种特殊的对象,咱们能够想象数据流就像一条河流,而数据就是河里的水,顺流而下。表明“流”的变量通常用“$”结尾,这是RxJS编程的一种约定,被成为“芬兰式命名法”。
    代码中的source$就是输入框的输入事件产生的数据流,咱们可使用pipe方法,像搭建“管道”同样对流中的数据进行加工,先使用map函数将事件对象转化成输入值,而后使用fllter方法过滤掉无效的输入,接着使用debounceTime控制数据向下流转的频率,最后使用switchMap把输入值转化成异步请求,整个数据流就构建完成了。
  4. 最后咱们使用数据流的subscribe方法添加对数据的操做,也就是将请求的结果输出到页面上。

    注意,这段代码咱们使用的所有变量都是用const声明的,所有是不可变的,也便是变量声明时是什么值,就永远是什么值,就像定义函数同样。相对于传统的指令式编程,RxJS的代码就是由一个一个不可变的函数组成,每一个函数只是对输入参数做出相应,而后返回结果,这样的代码写起来更加清爽,也更好维护前端工程师

RxJS结合了函数式响应式这两种编程思想,为了更深刻的了解RxJS,先来介绍一下什么是函数式编程和响应式编程。

函数式编程

函数式编程(Functional Porgramming)是一种编程范式,就像“面向对象编程”同样,是一种编写代码的“方法论”,告诉咱们应该如何思考和解决问题。不一样于面向对象编程,函数式编程强调使用函数来解决问题。

这里有两个问题:

  1. 任何语言都支持函数式编程么?并非,可以支持函数式编程的语言至少要知足“函数是一等公民(First Class)”这个要求,意思是函数能够被赋值给一个变量,而且能够做为参数传递给另外一个函数,也能够做为另外一个函数的返回值。显然JavaScript知足这个条件。
  2. 函数式编程里的函数有什么特别之处?函数式编程里要求函数知足如下几个要求:声明式、纯函数、数据不可变

声明式(Declarative)

与之对应的是命令式编程,也是最多见的编程模式。

举个例子,咱们但愿写个函数,把数组中的每一个元素乘以2,使用命令式编程,大概是这个样子的:

function double(arr) {
    const result = []
    for(let i=0,l=arr.length;i<l;i++) {
        result.push(arr[i] * 2)
    }
    return result
}

咱们将整个逻辑过程完整描述了一遍,完美。

但若是又来了一个新需求,实现一个新函数,把数组中每一个元素加1,简单,再来一遍:

function addOne(arr) {
    const result = []
    for(let i=0,l=arr.length;i<l;i++) {
        result.push(arr[i] + 1)
    }
    return result
}

是否是感受哪里不对?double和addOne百分之九十的代码彻底同样,“重复的代码是万恶之源。”咱们应该想办法改进一下。

这里就体现了命令式编程的一个问题,程序按照逻辑过程来执行,可是不少问题都有类似的模式,好比上面的double和addOne。很天然咱们想把这个模式抽象一下,减小重复代码。

接下来咱们使用JavaScript的map函数来重写double和addOne:

function double(arr) {
    return arr.map(function(item) { return item * 2 })
}

function addOne(arr) {
    return arr.map(function(item) { return item + 1 })
}

重复代码所有被封装到map函数中。而咱们须要作的只是告诉map函数应该如何映射数据,这就是声明式编程。相比较以前的代码,这样的代码更容易维护。

若是使用箭头函数,代码还能够进一步简化:

const double = arr => arr.map(item => item * 2)

const addOne = arr => arr.map(item => item + 1)

注意以上两个函数的返回结果都是一个新的数组,而并无对原数组进行修改,这符合函数式编程的另一个要求:纯函数

纯函数(Pure Function)

纯函数是指知足如下两个条件的函数:

  1. 相同的参数输入,返回相同的输出结果;
  2. 函数内不会修改任何外部状态,好比全局变量或者传入的参数对象;

举个栗子:

const arr = [1, 2, 3, 4, 5]

arr.slice(0, 3) // [1, 2, 3]

arr.slice(0, 3) // [1, 2, 3]

arr.slice(0, 3) // [1, 2, 3]

JavaScript中数组的slice方法无论执行几回,返回值都相同,而且没有改变任何外部状态,因此slice就是一个纯函数。

const arr = [1, 2, 3, 4, 5]

arr.splice(0, 3) // [1, 2, 3]

arr.splice(0, 3) // [4, 5]

arr.slice(0, 3) // []

相反,splice方法每次调用的结果就不一样,由于splice方法改变了全局变量arr的值,因此splice就不是纯函数。

不纯的函数每每会产生一些反作用(Side Effect),好比如下这些:

  1. 改变全局变量;
  2. 改变输入参数引用对象;
  3. 读取用户输入,好比调用了alert或者confirm函数;
  4. 抛出一个异常;
  5. 网络I/O,好比发送了一个AJAX请求;
  6. 操做DOM;

使用纯函数能够大大加强代码的可维护性,由于固定输入老是返回固定输出,因此更容易写单元测试,也就更不容易产生bug。

数据不可变(Immutability)

数据不可变是函数式编程中十分重要的一个概念,意思是若是咱们想改变一个变量的值,不是直接对这个变量进行修改,而是经过调用函数,产生一个新的变量。

若是你是一个前端工程师,确定已经对数据不可变的好处深有体会。在JavaScript中,字符串(String),数字(Number)这两种类型就是不可变的,使用他们的时候每每不容易出错,而数组(Array)类型就是可变的, 使用数组的pop、push等方法都会改变原数组对象,从而引起各类bug。

注意,虽然ES6已经提出了使用const声明一个常量(不可变数据),可是这只能保证声明的对象的引用不可改变,而这个对象自身仍然能够变化。好比用const声明一个数组,使用push方法仍然能够像数组中添加元素。

和面向对象编程相比,面向对象编程更倾向把状态的改变封装到对象内部,以此让代码更清晰。而函数式编程倾向数据和函数分离,函数能够处理数据,但不改变原数据,而是经过产生新数据的方式做为运算结果,以此来尽可能减小变化的部分,让咱们的代码更清晰。

响应式编程

和函数式编程相似,响应式编程(Reactive Programming)也是一种编程的范式。从设计模式的角度来讲,响应式编程就是“观察者模式”的一种有效实践。简单来讲,响应式编程指当数据发生变化时,主动通知数据的使用者这个变化

不少同窗都使用过vue框架开发,vue中很出名的数据双向绑定就是基于响应式编程的设计思想实现的。当咱们在经过v-bind绑定一个数据到组件上之后,无论这个数据什么时候发生变化,都会主动通知绑定过的组件,使咱们开发时能够专一处理数据自己,而不用关心如何同步数据。

而在相应时编程里最出名的框架就是微软开发的Reactive Extension。这套框架旨在帮助开发者解决复杂的异步处理问题。咱们的主角RxJS就是这个框架的JS版本。

怎么使用RxJS

安装

npm install rxjs

导入

import Rx from "rxjs";

请注意,这样导入会将整个RxJS库所有导入进来,而实际项目未必会用上Rxjs的所有功能,所有导入会让项目打包后变得很是大,咱们推荐使用深链(deep link)的方式导入Rxjs,只导入用的上的功能,好比咱们要使用Observable类,就只导入它:

import { Observable } from "rxjs/Observable";

实际项目中,按需导入是一个好办法,可是若是每一个文件都写一堆import语句,那就太麻烦了。因此,更好的实践是用一个文件专门导入RxJS相关功能,其余文件再导入这个文件,把RxJS导入工做集中管理

篇幅有限,下一讲将会讲解RxJS中几个核心概念,欢迎各位留言拍砖~

相关文章
相关标签/搜索