最近用 redux-observable
搭建了一個樣板項目,起先我就被人安利過這個庫,因爲本身工做的關係,一直沒能用上,恰巧最近項目不緊,遂搭一個簡單項目來瞅瞅,下面就請跟着個人步伐一步一步的探索這個庫的奧祕。html
redux-observable
背景這個庫是基於 rxjs
基礎上,爲 redux
提供的異步解決方案。react
redux
的異步流webpack
本來 redux
的 action creator
只提供一個同步的 action
但隨着業務的擴展,在某個場景下須要異步的 action
來延時調用 dispatch
。 經典的庫有 redux-thunk
,redux-saga
以及redux-observable
。git
redux-thunk
redux-thunk
的代碼很短,很巧妙的使用了 redux
的 applyMiddleware
中間件模式,它讓 action creator
不只能夠輸出 plain object
,也能夠輸出一個 function
來處理 action
,而這個 function
傳遞的參數就是 上下文的 dispatch
,當這個 function
在某個時段執行時,就能夠實現延時觸發 dispatch
了。github
**這個就是一個典型的函數式編程的案例,巧用了閉包,讓 dispatch
方法在函子內沒有被銷燬。 **web
redux-thunk
及其 applyMiddleware
源碼解讀ajax
可是這也是有必定的缺點的,就拿經常使用的 ajax
請求來講,每一個 action creator
輸出的 function
不盡相同,異步操做分散,並且邏輯也變幻無窮,action
多了,就不易維護了。編程
redux-saga
redux-saga
是另外一種異步流,不過它的 action
是統一形式的,而且會集中處理異步操做。redux
能夠理解 redux-saga
作了一個監聽器,專門監聽 action
,此處的 action
就是 plain object
,當接收到 UI 觸發了某個 action
時, redux-saga
就會觸發相應的 effects
來處理對應的反作用函數,這個函數返回的也是一個 plain object
的 action
給 reducer
。api
這樣作的好處是,redux-saga
接收了異步函數的管理,將複雜的業務邏輯部分與 redux
解耦,這也是 redux
設計的初衷,action
始終是 plain object
,並且 redux-saga
提供了很多工具函數來處理異步流,極大的方便了開發者處理異步。
網上有諸多教程,這裏就不一一贅述了。
不過有點鬱悶的就是 redux-saga
使用的是 generator
,寫起來還要在 function
那裏加個 *
,在我我的看來很是的不習慣,就是特別的彆扭。
redux-observable
redux-observable
和 redux-saga
有些相似,能夠理解爲它是將 action
看成便是 observable
也是 observer
(發佈者與訂閱者),就是 rxjs
的 Subject
對象,他是數據流的中轉站,可以訂閱上游數據流,也能被一個或者多個下游訂閱。
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
複製代碼
從上面的目錄結構就能看出,比常規的 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 簡易流程:
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
方法就是得到 action
的 type
是 操做符 oftype
返回的一致,則繼續管道後面的操做,switchMap
是一個高階的操做符,它通常用在 ajax
網絡服務請求上,主要處理多個內部 Observable
對象產生併發的狀況下,只訂閱最後一個數據源,其餘的都退訂,這樣的操做符,很是適合網絡請求。
這個網絡請求就是獲取 github 的 api,當獲取數據後,調用 action creator
方法傳遞獲取的數據,這個時候並無返回一個真正的 plain object
,而是一個最終的 action$
數據流,觸發 subscribe
的 store.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 高階方法,它相似與 reducers
的 combineReducers
。
那麼,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 中間件中,這樣,就能接收到上下文的 action
和 dispatch
,不過要注意的是,epicMiddleware
要在store
設置以後,執行 run 方法,這和 redux-saga
一致。
這樣,基本上 redux
和 redux-observable
組合的基本操做已經差很少了,reducer
的操做基本不變
yarn && yarn start
# localhost:8000
複製代碼
喜歡的話給個 star 啊!
最後說下學習路徑:
函數式編程
從頭開始學編程吧,用函數式,純函數的那種。
redux
源碼閱讀
大牛的做品,閉包用的爐火純青,各類高階函數,精妙絕倫的操做大大下降了代碼量,更能看到函數式編程的妙處。
rxjs
及其操做符
響應式編程的系統學習,但沒必要要全部操做符都過一遍,這裏推薦一本書 《深刻淺出 rxjs》,不過書裏的版本是 v5 的,官網是 v6 的,除了一些改變外,原理都是相同的。