標籤:

在React中使用RxJS

上期分享了關於RxJS的入門知識-初探RxJS,這期將會在此基礎上,進一步分享RxJS的實戰內容。在React中使用RxJS,需要藉助Redux的中間件Redux-obervable。利用RxJS的非同步數據流特性,處理React中的非同步邏輯將會變得非常的輕鬆。

在使用Redux-observable前,你需要對React,Redux,RxJS有一定的了解和實踐經驗。在Redux-observable中,有一個很重要的概念就是epics,它是函數的形式,其函數簽名如下:

function (action$: Observable<Action>, store: Store): Observable<Action>;n

主要作用就是,接收Redux中的action,利用RxJS強大靈活的操作符,將action進行一些變換(如:網路請求),最後再返回actions,注意這裡是actions,也就是可能會發出多個action。發出的action,將會自動調用store.dispatch()進行分發。

下面是一個簡單的Epic 例子:

const pingEpic = action$ =>naction$.filter(action => action.type === PING)n .mapTo({ type: PONG });n n

注意,為什麼 action$ 是以美元符結尾呢? 這是 RxJS 的基本公約用來標示流。

pingEpic這個函數會監聽type為PING的 action,然後投射為新的 type為Pong的action。這個例子功能上相當於做了這件事情:

dispatch({type: PING})ndispatch({type: PONG})n

具體的流程大致是這樣:首先通過store.dispatch({type: PING})發出一個同步的action;這個action會流經epics函數,epics函數會根據action的type來過濾自己需要處理的action,然後自動調用store.dispatch發出新的action,同時,老的action同樣會被發出;reducer接收到兩個action,返回新的state。

下面看一個更複雜的例子:

const pingEpic = action$ =>naction$.ofType(PING)n .delay(1000) n .mapTo({ type: PONG });n

上面的例子不難理解,因為RxJS 和Redux-observable中的API都是聲明式,這個epic會通過ofType這個操作符攔截type為PING的action,然後非同步等待1000s,在發出一個type為PONG的action,同時記住,type為PING的action並沒有被阻斷,也被發出了。

在實際使用中場景中,有很大一部分都是跟網路請求相關的,所以下面的例子非常的實用:

import { ajax } from rxjs/observable/dom/ajax;nn// action creatorsnconst fetchUser = username => ({ type: FETCH_USER, payload: username });nconst fetchUserFulfilled = payload => ({ type: FETCH_USER_FULFILLED, payload });nn// epicnconst fetchUserEpic = action$ =>n action$.ofType(FETCH_USER)n .mergeMap(action =>n ajax.getJSON(`https://api.github.com/users/${action.payload}`)n .map(response => fetchUserFulfilled(response))n );n

藉助RxJS的ajax操作符ajax,使得處理網路請求也變得非常的方便。同時epics除了第一個為action的參數,還有第二個參數store,這就變得非常有用了。例如:當發起某個網路請求,接收到響應時,我們不僅需要將該響應的數據填充到頁面中,也需要通知頁面將loading的動畫停止掉,所以調用store.dispatch來手動發出新的action,改變state。同時也可以調用store.getState()來同步獲取當前的state。

下面是我寫過的一個比較複雜的例子,我們將一點一點的剖析其中的邏輯

const chooseToDispatch = (data, store) => {n if (data.code === 200) {n store.dispatch(loginSuccess({data: data.payload}))n } else {n store.dispatch(loginFailed({message: data.message}))n }n}nnconst userLoginEpic = (action$, store)=>naction$.ofType(ActionTypes.LOGIN_START)n.debounce(3000)n.switchMap(action => n ajax.post(/login, action.payload)n .map(data => data.response)n .map(data => chooseToDispatch(data, store))n .takeUntil(action$.ofType(ActionTypes.LOGIN_CANCEL))n .catch(() => Observable.of(loginFailed({message: network error})))n)n

  1. userLoginEpic接收type為LOGIN_START的action;
  2. 然後使用debounce操作符在3000ms內不再響應type為LOGIN_START的action;
  3. 再使用ajax操作符發起網路請求;
  4. 將得到的響應由data => 變換為data.response方便處理;
  5. 在使用map操作符以及chooseToDispatch對響應進行判斷,這個時候就用到了store,根據不同的響應dispatch不同的action;
  6. 然後是taskUtil操作符。有時我會在面試時問候選者如何cancel一個action,來考察對redux掌握情況,一種比較好的方法就是藉助這裡的takeUtil操作符,當請求發出之後還未得到響應時,如果接收到LOGIN_CANCEL的action,將會discard掉之前請求對應的響應
  7. 最後是catch操作,用戶捕獲請求中產生的錯誤

上面的例子非常的容易理解,因為一切都是聲明式的。說了這些,大概讀者應該能理解Redux-observable能做些什麼。接下來是如何在項目中使用。

首先需要 安裝rxjs,redux-obervable

npm install --save redux-observable rxjsn

redux-observable強依賴於redux和rxjs,所以都需要安裝,接下來是配置:

import { combineEpics } from redux-observable;nimport { combineReducers } from redux;nimport ping, { pingEpic } from ./ping;nimport users, { fetchUserEpic } from ./users;nnexport const rootEpic = combineEpics(n pingEpic,n fetchUserEpicn);nnexport const rootReducer = combineReducers({n ping,n usersn});n

這裡推薦為每一個非同步操作創建一個文件,然後編寫epics函數,再導出到一個文件中,藉助comibeEpics函數,將所有的epcis拼接成root epic進行導出,最後是配置store

import { createStore, applyMiddleware } from redux;nimport { createEpicMiddleware } from redux-observable;nimport { rootEpic, rootReducer } from ./modules/root;nnconst epicMiddleware = createEpicMiddleware(rootEpic);nnexport default function configureStore() {n const store = createStore(n rootReducer,n applyMiddleware(epicMiddleware)n );nnn return store;n}n

使用createEpicMiddleware函數將root epic包裝成redux中需要的middleware,然後再使用createStore創建store就可以。

為了方便開發調試,也可以配置redux dev-tools,就像下面這樣:

import { compose } from redux; // and your other imports from beforenconst epicMiddleware = createEpicMiddleware(pingEpic);nnconst composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ||n compose;nnconst store = createStore(pingReducer,n composeEnhancers(n applyMiddleware(epicMiddleware)n )n);n

如果你有redux的開發經驗,配置應該很輕鬆就搞定了。

最後,總結一下,我認為redux-observable有三個有點:

  1. 低侵入性。在redux中 ,無論之前用的是什麼 async middleware,只要該寫非同步的部分,同步的邏輯可以保持復用。
  2. 聲明式API。見名知意。
  3. 強大靈活的操作符。藉助RxJS強大的操作符,能夠完成許多看似不可能的操作。

參考:Introduction · redux-observable


推薦閱讀:

Redux-Saga 初識和總結
redux到mobx到dob……前端發展好快?
構建離線優先的 React 應用
redux 中的 state 樹太大會不會有性能問題?

TAG:React | RxJS | Redux |