rxjs6學習筆記----結合react,redux使用

rxjs版本是當前2018-07-23最新, 6.2.2。本文寫了rxjs的基本知識和結合react的應用,ajax怎麼用rx控制的套路,錯誤處理,以及常用操作符的細節。如switchMap,concatMap, flatMap的區別等。

"rxjs": "^6.2.2"

導入模塊的區別

rxjs已經出到6了,5.5有一個大更新,6又有很多更新。首先是自動rethrow的錯誤不再是同步的,而是異步的,其次是模塊導入的方法。現在只分爲兩類,前一類,如of, empty等方法,直接從rxjs裏導入。
這裏寫圖片描述

正確的導入方式:

import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent, combineLatest, SubscriptionLike, PartialObserver } from 'rxjs';
import { map, filter, scan } from 'rxjs/operators';
import { webSocket } from 'rxjs/webSocket';
import { ajax } from 'rxjs/ajax';
import { TestScheduler } from 'rxjs/testing';

注意concat和combineLatest都要從rxjs裏Import

pipe

rxjs5.5以上使用pipe方法而非鏈式調用。所有鏈式的起點都用Pipe來開始,之後的每一次處理都用逗號隔開。例子

// rxjs v5.5+
const source$ = range(1, 3)
const liveStreaming$ = source$.pipe(
  flatMap(val => of(val).pipe(
    flatMap(val => from(fetch(`http://swapi.co/api/people/${val}`))),
    flatMap(res => from(res.json())),
    map(res => res.name),
    tap(console.log)
  ))
)

//rxjs old version
let stream$ = Rx.Observable
.of(1,2,3)
.flatMap((val) => {
  return Rx.Observable
            .of(val)
            .ajax({ url : url })
            .map((e) => e.response )
})

再比如一個concat的例子

import {
  interval, concat,
} from 'rxjs'
import {
  take, map,  tap,
} from 'rxjs/operators'

function addItem(val) {
  const node = document.createElement('li')
  const textnode = document.createTextNode(val)
  node.appendChild(textnode)
  document.getElementById('output').appendChild(node)
}
const s1$ = interval(1000).pipe(
  map(val => `s1   ${val}`),
  take(5)
)

const s2$ = interval(500).pipe(
  map(val => `s2  ${val}`),
  take(3)
)

const liveStreaming$ = concat(s1$, s2$).pipe(
  tap(console.log)
)

const subscription = liveStreaming$.subscribe(
  data => addItem(`${data}`),
  err => console.log(err),
  () => console.log('completed')
)

操作符

過濾操作符

distinctUntilChanged

會接收到上一次和這一次的對象作爲參數,默認的比較是===,可以傳入一個comparer function來自定義。例如,比較兩個Object時,可以用R.equals
這個例子來自官網

* @example <caption>An example using a compare function</caption>
 * interface Person {
 *    age: number,
 *    name: string
 * }
 *
 * Observable.of<Person>(
 *     { age: 4, name: 'Foo'},
 *     { age: 7, name: 'Bar'},
 *     { age: 5, name: 'Foo'})
 *     { age: 6, name: 'Foo'})
 *     .distinctUntilChanged((p: Person, q: Person) => p.name === q.name)
 *     .subscribe(x => console.log(x));
 *
 * // displays:
 * // { age: 4, name: 'Foo' }
 * // { age: 7, name: 'Bar' }
 * // { age: 5, name: 'Foo' }
 *

在保存表單時,也可以用它來過濾xhr請求,這是redux裏的寫法

// epics.js  通常文件名叫epics
const openProject = actions$ => actions$.ofType(OPEN_PROJECT).pipe(
  distinctUntilChanged(R.equals),
  flatMap(
    ({ projectUid }) => request$.post('/api/project/open-status', { projectUid }).pipe(
      map(R.path(['data'])),
     ...

轉換操作符

switchMap, flatMap, concatMap 的區別

這三個操作符都是map 與其他操作符的結合。這裏簡單總結了下,詳見30 天精通RxJS(18): Observable Operators - switchMap, mergeMap, concatMap

concatMap = .map(fn).concatAll()
前一個完成後,第二個纔會發出,有順序
switchMap = .map(fn).switch()
下一個發出,則退訂前一個。前一個成功也好,失敗也好,不會造成任何side-effect了
flatMap = .map(fn).flatAll()  // 其實就是mergeMap, mergeAll()
有攤平的observable的效果,並行處理多個流。可能重疊。

三個操作符都可以傳入第二個selector callback 參數,flatMap還可以傳第三個參數限制並行處理的數量。

source : -----------c--c------------------...
        concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0-1-2-0-1-2---------...

source : -----------c--c-----------------...
        concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0--0-1-2-----------...

source : -----------c-c------------------...
        concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0-(10)-(21)-2----------...

錯誤處理

注意catchError本身可以再返回一個流,即catchError之後還可以再catch或者繼續做事情。
所以順序應該是

catchError(fn),
takeUntil(LOCATION_CHANGE$),
finalize(fn)

如果是request失敗,需要再次嘗試。retry()會立即發出,而在網絡不穩定的時候,應該隔一會兒再發請求。需要用到delay 和 retryWhen, 以及 scan 來控制retry 的次數。參見錯誤處理 · RxJS 5 基本原理

const ATTEMPT_COUNT = 1
const DELAY_INTERVAL = 200

...

stream$.pipe(
    pluck('data'),
    retryWhen(e => e.pipe(
      scan((errorCount, err) => {
        if (errorCount >= ATTEMPT_COUNT) {
          throw err
        }
        return errorCount + 1
      }, 0),
      delay(DELAY_INTERVAL)
    )),
    catchError((err) => {
      console.error(`Request ${url} Error `, err.stack)
      return throwError(JSON.stringify(err.response.data))
    })

subject

這個話題就多了,包括怎麼warm up 一個 observable, 把它從冷的變成熱的。什麼什麼。。。
大意就是,subject是一個代理人的角色,它的subscribe 是把諸多 observer 加入到一個數組中,在next的時候依次通知這些observers 。作爲一箇中間代理人,它同時擁有 Observer 和 Observable 的行爲。

學習資源

rxjs5的中文gitbook,略老但排版好看,適合入門
rxjs的github,在reactiveX下面,已經到6版本了 RxJS: Reactive Extensions For JavaScript
rxjs 英文官網,關於如何遷移到版本6
rxjs 中文翻譯官方文檔 比較全,翻譯的一般般吧。。。
一個博客上關於rxjs的系列入門文章,英文的
hot-cold-observables,什麼是熱的觀察對象,什麼是冷的observable,怎麼warm up

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章