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中幾個核心概念,歡迎各位留言拍磚~

相關文章
相關標籤/搜索