redux-observable 使用小記

最近用 redux-observable 搭建了一個樣板項目,起先我就被人安利過這個庫,因爲本身工做的關係,一直沒能用上,恰巧最近項目不緊,遂搭一個簡單項目來瞅瞅,下面就請跟着個人步伐一步一步的探索這個庫的奧祕。html

redux-observable 背景

這個庫是基於 rxjs 基礎上,爲 redux 提供的異步解決方案。react

redux 的異步流webpack

本來 reduxaction creator 只提供一個同步的 action 但隨着業務的擴展,在某個場景下須要異步的 action 來延時調用 dispatch。 經典的庫有 redux-thunkredux-saga以及redux-observablegit

  • redux-thunk

redux-thunk的代碼很短,很巧妙的使用了 reduxapplyMiddleware 中間件模式,它讓 action creator 不只能夠輸出 plain object,也能夠輸出一個 function 來處理 action,而這個 function 傳遞的參數就是 上下文的 dispatch,當這個 function 在某個時段執行時,就能夠實現延時觸發 dispatch 了。github

**這個就是一個典型的函數式編程的案例,巧用了閉包,讓 dispatch 方法在函子內沒有被銷燬。 **web

redux-thunk 及其 applyMiddleware 源碼解讀ajax

redux-thunk 流程

可是這也是有必定的缺點的,就拿經常使用的 ajax 請求來講,每一個 action creator 輸出的 function 不盡相同,異步操做分散,並且邏輯也變幻無窮,action 多了,就不易維護了。編程

  • redux-saga

redux-saga 是另外一種異步流,不過它的 action 是統一形式的,而且會集中處理異步操做。redux

能夠理解 redux-saga 作了一個監聽器,專門監聽 action ,此處的 action 就是 plain object ,當接收到 UI 觸發了某個 action 時, redux-saga 就會觸發相應的 effects 來處理對應的反作用函數,這個函數返回的也是一個 plain objectactionreducerapi

這樣作的好處是,redux-saga 接收了異步函數的管理,將複雜的業務邏輯部分與 redux 解耦,這也是 redux 設計的初衷,action 始終是 plain object,並且 redux-saga 提供了很多工具函數來處理異步流,極大的方便了開發者處理異步。

redux-saga 簡易流程

網上有諸多教程,這裏就不一一贅述了。

不過有點鬱悶的就是 redux-saga 使用的是 generator,寫起來還要在 function 那裏加個 *,在我我的看來很是的不習慣,就是特別的彆扭。

  • redux-observable

redux-observableredux-saga 有些相似,能夠理解爲它是將 action 看成便是 observable 也是 observer(發佈者與訂閱者),就是 rxjsSubject對象,他是數據流的中轉站,可以訂閱上游數據流,也能被一個或者多個下游訂閱。

redux-observable 將從 ui 觸發的 action 轉化爲一個數據流,而且訂閱它。當數據流有數據發出時,這個流的數據管道中設置了對此數據流作的一系列的操做符,或者是高階 observable,數據流經過管道後,將最終的流轉成 action

上述所說的管道就是由 rxjs 提供的操做符組合

redux 中使用 redux-observable

如今開始作一個簡易的項目(調用 github api 獲取用戶的頭像和名稱):

  • 安裝 redux 全家桶

常規的 redux 項目所需的庫,基本上都會用到

yarn add react react-dom redux react-redux react-router redux-logger ...
yarn add -D webpack webpack-cli ...
複製代碼
  • 安裝 redux-observable
yarn add rxjs redux-observable

# ts聲明庫
yarn add -D @types/rx
複製代碼
  • 目錄結構
.
├── actions
├── components
├── constants
├── epics
├── reducers
├── types
└── utils
├── index.html
├── index.tsx
├── routes.tsx
├── store.ts
複製代碼
  • 解讀 Epics

從上面的目錄結構就能看出,比常規的 redux 項目多了一個 epics 目錄,這個目錄是存放什麼文件呢。

redux-observable 的核心就是 Epics ,它是一個函數,接收一個 action (plain object) ,返回一個 action 流,它是一個 Observable 對象。

函數簽名:

function (action$: Observable<Action>, store: Store): Observable<Action>; 複製代碼

Epics 函數出來的 action 已是一個 Observable 對象了,是一個上游數據流了,能夠被各類 rxjs 操做符操做了。

數據流的終端就是一個訂閱者,這個訂閱者只作一件事兒,就是被 store.dispatch 分發至 reducer

epic(action$, store).subscribe(store.dispatch);
複製代碼

redux-observable 簡易流程:

redux-observable 簡易流程

  • 編碼
import { USER } from "@constants";
import { createAction } from "typesafe-actions";

export namespace userActions {
  export const getGitHubUser = createAction(USER.GITHUB_USER_API);

  export const setUserInfo = createAction(USER.SET_USER_INFO, resolve => user =>
    resolve(user)
  );
}
複製代碼

創建一個 user 的操做 action,定義兩個 action

epic 文件:

import { ofType, ActionsObservable } from "redux-observable";
import { throwError } from "rxjs";
import { switchMap, map, catchError } from "rxjs/operators";
import { ajax } from "rxjs/ajax";
import { getType } from "typesafe-actions";
import { userActions } from "@actions";

const url = "https://api.github.com/users/soraping";

export const userEpic = (action$: ActionsObservable<any>) =>
  action$.pipe(
    ofType(getType(userActions.getGitHubUser)),
    switchMap(() => {
      return ajax.getJSON(url).pipe(
        map(res => userActions.setUserInfo(res)),
        catchError(err => throwError(err))
      );
    })
  );
複製代碼

創建一個 userEpic ,它是一個高階函數,這個高階函數攜帶的參數就是 action$ ,它就是一個上游數據流,這個函數的基礎邏輯就是一個 rxjs 的通常操做了。

上游 action$ 的數據管道中,監聽 action 的變化,當 getType 方法就是得到 actiontype 是 操做符 oftype 返回的一致,則繼續管道後面的操做,switchMap 是一個高階的操做符,它通常用在 ajax 網絡服務請求上,主要處理多個內部 Observable 對象產生併發的狀況下,只訂閱最後一個數據源,其餘的都退訂,這樣的操做符,很是適合網絡請求。

這個網絡請求就是獲取 github 的 api,當獲取數據後,調用 action creator 方法傳遞獲取的數據,這個時候並無返回一個真正的 plain object ,而是一個最終的 action$ 數據流,觸發 subscribestore.dispatch(action) 方法,將 plain action 送至 reducer

typesafe-actions 庫是一個 action 封裝庫,簡化了 action 的操做,它和 redux-actions 很像,可是typesafe-actions這個庫對 epic 支持得很好。

整合多個epic

import { combineEpics } from "redux-observable";
import { userEpic } from "./user";
export const rootEpic = combineEpics(userEpic);
複製代碼

combineEpics 方法用來整合多個 epic 高階方法,它相似與 reducerscombineReducers

那麼,epic 方法已經有了,redux-observable 畢竟是一箇中間件,它在 store 中的操做:

import { createStore, applyMiddleware } from "redux";
import { createEpicMiddleware } from "redux-observable";
import { composeWithDevTools } from "redux-devtools-extension";
import { routerMiddleware } from "connected-react-router";
import { createLogger } from "redux-logger";
import { createBrowserHistory } from "history";
import { rootReducer } from "./reducers";
import { rootEpic } from "./epics";

export const history = createBrowserHistory();

const epicMiddleware = createEpicMiddleware();

const middlewares = [
  createLogger({ collapsed: true }),
  epicMiddleware,
  routerMiddleware(history)
];

export default createStore(
  rootReducer(history),
  composeWithDevTools(applyMiddleware(...middlewares))
);

// run 方法必定要在 createStore 方法以後
epicMiddleware.run(rootEpic);
複製代碼

epicMiddleware註冊到 redux 中間件中,這樣,就能接收到上下文的 actiondispatch,不過要注意的是,epicMiddleware要在store設置以後,執行 run 方法,這和 redux-saga一致。

這樣,基本上 reduxredux-observable 組合的基本操做已經差很少了,reducer 的操做基本不變

上述例子的 github 源碼

yarn && yarn start

# localhost:8000
複製代碼

喜歡的話給個 star 啊!

推薦學習路徑

最後說下學習路徑:

函數式編程

從頭開始學編程吧,用函數式,純函數的那種。

redux 源碼閱讀

大牛的做品,閉包用的爐火純青,各類高階函數,精妙絕倫的操做大大下降了代碼量,更能看到函數式編程的妙處。

redux源碼閱讀參考

rxjs 及其操做符

響應式編程的系統學習,但沒必要要全部操做符都過一遍,這裏推薦一本書 《深刻淺出 rxjs》,不過書裏的版本是 v5 的,官網是 v6 的,除了一些改變外,原理都是相同的。

相關文章
相關標籤/搜索