RxJS: 簡單入門

Introduction to RxJS

1. 前言

1.1 什麼是RxJS

RxJSReactiveX編程理念的JavaScript版本。ReactiveX來自微軟,它是一種針對異步數據流的編程。簡單來講,它將一切數據,包括HTTP請求,DOM事件或者普通數據等包裝成流的形式,而後用強大豐富的操做符對流進行處理,使你能以同步編程的方式處理異步數據,並組合不一樣的操做符來輕鬆優雅的實現你所須要的功能。html

1.2 RxJS可用於生產嗎?

ReactiveX由微軟於2012年開源,目前各語言庫由ReactiveX組織維護。RxJSGitHub上已有8782個star,目前最新版本爲5.5.2,並持續開發維護中,其中官方測試用例共計2699個。前端

誰在使用Rx

1.3 RxJS對項目代碼的影響?

RxJS中的流以Observable對象呈現,獲取數據須要訂閱Observable,形式以下:git

const ob = http$.getSomeList(); //getSomeList()返回某個由`Observable`包裝後的http請求
ob.subscribe((data) => console.log(data));
//在變量末尾加$表示Observable類型的對象。

以上與Promise相似:es6

const promise = http.getSomeList(); // 返回由`Promise`包裝的http請求
promise.then((data) => console.log(data));

實際上Observable能夠認爲是增強版的Promise,它們之間是能夠經過RxJSAPI互相轉換的:github

const ob = Observable.fromPromise(somePromise); // Promise轉爲Observable
const promise = someObservable.toPromise(); // Observable轉爲Promise

所以能夠在Promise方案的項目中安全使用RxJS,並可以隨時升級到完整的RxJS方案。ajax

1.4 RxJS會增長多少體積?

RxJS(v5)整個庫壓縮後約爲140KB,因爲其模塊化可擴展的設計,所以僅需導入所用到的類與操做符便可。導入RxJS經常使用類與操做符後,打包後的體積約增長30-60KB,具體取決於導入的數量。編程

不要用 import { Observable } from 'rxjs'這種方式導入,這會導入整個 rxjs庫,按需導入的方式以下:
import { Observable } from 'rxjs/Observable' //導入類
import 'rxjs/add/operator/map' // 導入實例操做符
import 'rxjs/add/observable/forkJoin' // 導入類操做符

2. RxJS快速入門

2.1 初級核心概念

  • Observable
  • Observer
  • Operator

Observable被稱爲可觀察序列,簡單來講數據就在Observable中流動,你可使用各類operator對流進行處理,例如:segmentfault

const ob = Observable.interval(1000);
ob.take(3).map(n => n * 2).filter(n => n > 2);

第一步代碼咱們經過類方法interval建立了一個Observable序列,ob做爲源會每隔1000ms發射一個遞增的數據,即0 -> 1 -> 2。第二步咱們使用操做符對流進行處理,take(3)表示只取源發射的前3個數據,取完第三個後關閉源的發射;map表示將流中的數據進行映射處理,這裏咱們將數據翻倍;filter表示過濾掉出符合條件的數據,根據上一步map的結果,只有第二和第三個數據會留下來。api

上面咱們已經使用同步編程建立好了一個流的處理過程,但此時ob做爲源並不會馬上發射數據,若是咱們在map中打印n是不會獲得任何輸出的,由於ob做爲Observable序列必須被「訂閱」纔可以觸發上述過程,也就是subscribe(發佈/訂閱模式)。數組

const ob = Observable.interval(1000);
ob.take(3).map(n => n * 2).filter(n => n > 0).subscribe(n => console.log(n));

結果:

2 //第2秒
4 //第3秒

上面代碼中咱們給subscribe傳入了一個函數,這實際上是一種簡寫,subscribe完整的函數簽名以下:

ob.subscribe({
    next: d => console.log(d),
    error: err => console.error(err),
    complete: () => console.log('end of the stream')
})

直接給subscribe傳入一個函數會被當作是next函數。這個完整的包含3個函數的對象被稱爲observer(觀察者),表示的是對序列結果的處理方式。next表示數據正常流動,沒有出現異常;error表示流中出錯,多是運行出錯,http報錯等等;complete表示流結束,再也不發射新的數據。在一個流的生命週期中,errorcomplete只會觸發其中一個,能夠有多個next(表示屢次發射數據),直到complete或者error

observer.next能夠認爲是Promisethen的第一個參數,observer.error對應第二個參數或者Promisecatch

RxJS一樣提供了catch操做符,err流入catch後,catch必須返回一個新的Observable。被catch後的錯誤流將不會進入observererror函數,除非其返回的新observable出錯。

Observable.of(1).map(n => n.undefinedMethod()).catch(err => {
    // 此到處理catch以前發生的錯誤
    return Observable.of(0); // 返回一個新的序列,該序列成爲新的流。
});

2.2 建立可觀察序列

建立一個序列有不少種方式,咱們僅列舉經常使用的幾種:

Observable.of(...args)

Observable.of()能夠將普通JavaScript數據轉爲可觀察序列,點我測試

Observable.fromPromise(promise)

Promise轉化爲Observable點我測試

Observable.fromEvent(elment, eventName)

DOM事件建立序列,例如Observable.fromEvent($input, 'click')點我測試

Observable.ajax(url | AjaxRequest)

發送http請求,AjaxRequest參考這裏

Observable.create(subscribe)

這個屬於萬能的建立方法,通常用於只提供了回調函數的某些功能或者庫,在你用這個方法以前先想一想能不能用RxJS上的類方法來建立你所須要的序列,點我測試

2.3 合併序列

合併序列也屬於建立序列的一種,例若有這樣的需求:進入某個頁面後拿到了一個列表,而後須要對列表每一項發出一個http請求來獲取對應的詳細信息,這裏咱們把每一個http請求做爲一個序列,而後咱們但願合併它們。
合併有不少種方式,例如N個請求按順序串行發出(前一個結束再發下一個);N個請求同時發出而且要求所有到達後合併爲數組,觸發一次回調;N個請求同時發出,對於每個到達就觸發一次回調。
若是不用RxJS,咱們會比較難處理這麼多情形,不只實現麻煩,維護更麻煩,下面是使用RxJS對上述需求的解決方案:

const ob1 = Observable.ajax('api/detail/1');
const ob2 = Observable.ajax('api/detail/2');
...
const obs = [ob1, ob2...];
// 分別建立對應的HTTP請求。
  1. N個請求按順序串行發出(前一個結束再發下一個)
Observable.concat(...obs).subscribe(detail => console.log('每一個請求都觸發回調'));
  1. N個請求同時並行發出,對於每個到達就觸發一次回調
Observable.merge(...obs).subscribe(detail => console.log('每一個請求都觸發回調'));
  1. N個請求同時發出而且要求所有到達後合併爲數組,觸發一次回調
Observable.forkJoin(...obs).subscribe(detailArray => console.log('觸發一次回調'));

3. 使用RxJS實現搜索功能

搜索是前端開發中很常見的功能,通常是監聽<input />keyup事件,而後將內容發送到後臺,並展現後臺返回的數據。

<input id="text"></input>
<script>
    var text = document.querySelector('#text');
    text.addEventListener('keyup', (e) =>{
        var searchText = e.target.value;
        // 發送輸入內容到後臺
        $.ajax({
            url: `/search/${searchText}`,
            success: data => {
              // 拿到後臺返回數據,並展現搜索結果
              render(data);
            }
        });
    });
</script>

上面代碼實現咱們要的功能,但存在兩個較大的問題:

  • 多餘的請求

當想搜索「愛迪生」時,輸入框可能會存在三種狀況,「愛」、「愛迪」、「愛迪生」。而這三種狀況將會發起 3 次請求,存在 2 次多餘的請求。

  • 已無用的請求仍然執行

一開始搜了「愛迪生」,而後立刻改搜索「達爾文」。結果後臺返回了「愛迪生」的搜索結果,執行渲染邏輯後結果框展現了「愛迪生」的結果,而不是當前正在搜索的「達爾文」,這是不正確的。

減小多餘請求數,能夠用 setTimeout 函數節流的方式來處理,核心代碼以下:

<input id="text"></input>
<script>
    var text = document.querySelector('#text'),
        timer = null;
    text.addEventListener('keyup', (e) =>{
        // 在 250 毫秒內進行其餘輸入,則清除上一個定時器
        clearTimeout(timer);
        // 定時器,在 250 毫秒後觸發
        timer = setTimeout(() => {
            console.log('發起請求..');
        },250)
    })
</script>

已無用的請求仍然執行 的解決方式,能夠在發起請求前聲明一個當前搜索的狀態變量,後臺將搜索的內容及結果一塊兒返回,前端判斷返回數據與當前搜索是否一致,一致才走到渲染邏輯。最終代碼爲:

<input id="text"></input>
<script>
    var text = document.querySelector('#text'),
        timer = null,
        currentSearch = '';

    text.addEventListener('keyup', (e) =>{
        clearTimeout(timer)
        timer = setTimeout(() => {
            // 聲明一個當前所搜的狀態變量
            currentSearch = '書'; 

            var searchText = e.target.value;
            $.ajax({
                url: `/search/${searchText}`,
                success: data => {
                    // 判斷後臺返回的標誌與咱們存的當前搜索變量是否一致
                    if (data.search === currentSearch) {
                        // 渲染展現
                        render(data);
                    } else {
                        // ..
                    }
                }           
            });
        },250)
    })
</script>

上面代碼基本知足需求,但代碼開始顯得亂糟糟。咱們來使用RxJS實現上面代碼功能,以下:

var text = document.querySelector('#text');
var inputStream = Rx.Observable.fromEvent(text, 'keyup') //爲dom元素綁定'keyup'事件
                    .debounceTime(250) // 防抖動
                    .pluck('target', 'value') // 取值
                    .switchMap(url => Http.get(url)) // 將當前輸入流替換爲http請求
                    .subscribe(data => render(data)); // 接收數據

RxJS能簡化你的代碼,它將與流有關的內部狀態封裝在流中,而不須要在流外定義各類變量來以一種上帝視角控制流程。Rx的編程方式使你的業務邏輯流程清晰,易維護,並顯著減小出bug的機率。

我的總結的經常使用操做符:

類操做符(一般爲合併序列或從已有數據建立序列)
合併 forkJoin, merge, concat
建立 of, from, fromPromise, fromEvent, ajax, throw
實例操做符(對流中的數據進行處理或者控制流程)
map, filter,switchMap, toPromise, catch, take, takeUntil, timeout, debounceTime, distinctUntilChanged, pluck
對於這些操做符的使用再也不詳細描述,請參閱網上資料。

中文官網 http://cn.rx.js.org/
附上我的翻譯的一些文章

最後打個廣告,螞蟻金服大安全風控+團隊招人,前端後臺都要,簡歷發送至huitong.zht@antfin.com。

參考文章:構建流式應用:RxJS 詳解

相關文章
相關標籤/搜索