在開發 React 應用的過程當中,咱們通常習慣使用 Redux 做爲狀態管理工具,由於這個工具足夠的簡單。而在通常的項目中,Redux經過中間件,提供了足夠的能力處理同步異步事件。而處理異步事件的中間件有不少,Redux-observable 就是一個,它經過和 Rxjs 深度結合,提供了一種很棒的方式來處理異步事件以及反作用。react
若是你瞭解過 NgRx,那麼,下面的介紹的方法您確定不會陌生,首先,我容我「盜用」 NgRx 的狀態圖。git
爲了解決 Side-Effect,你們都想到不少的方法:github
Redux-thunk 經過中間件加強了dispatch方法,使得其輸入能夠包括函數對象,經過輸入函數,咱們能夠在函數中引入反作用,並對其進行處理。然而,action 和 effect 糅雜在一塊兒,並不能很好的管理反作用。同時,action 的數據形式受到了必定程度的破壞,在我看來,action應該是一個純粹的 plain object,而不是函數對象。ajax
Redux-sega 則是更接近與 Redux-observable 的一種解決方式。使用觀察者模式能夠對輸入的action進行觀察,將 Effect 隔離出來。經過 Generator 函數,並將異步操做(代碼)同步化。因爲是同步操做,能夠經過 try/catch 進行錯誤處理。typescript
RO 跟 Redux-sega 的操做思想相似,可是 RO 引入了我比較喜歡的 Rxjs,經過 Rxjs 的流概念帶來的強大的抽象能力,管理反作用變得垂手可得,使得 RO 在管理數據流方面有着自然的優點。redux
在 RO 有這麼一個概念,叫 Epic。在 RO 的官網上,是這樣描述它的:api
It is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.安全
簡而言之,Epic就是一個action操做流的函數。這個函數的簽名是這樣的:bash
function (action$: Observable<Action>, state$: StateObservable<State>): Observable<Action>;
複製代碼
Epic 操做流的的方式是這樣的(直接用了官網的例子了):app
const pingEpic = action$ => action$.pipe(
filter(action => action.type === 'PING'),
mapTo({ type: 'PONG' })
);
複製代碼
能夠用 ofType() 這個操做符將特定 action 過濾:
const fetchAction
const fetchUserEpic = action$ => action$.pipe(
ofType(FETCH_USER),
mergeMap(action => ajax.getJSON(`https://api.github.com/users/${action.payload}`)),
map(response => fetchUserFulfilled(response)))
);
複製代碼
在 Epic 的基礎上,RO 引伸出了 Root Epic 這個概念,Root Epic 包含了全部的 Epic。每個 Epic 經過一個 merge 操做符 而它最終會被 epicMiddleware 這個中間件調用。
const rootEpic = combineEpics(
pingEpic,
fetchUserEpic
);
// ... 此處省略 rootReducer
const epicMiddleware = createEpicMiddleware();
export default function configureStore() {
const store = createStore(
rootReducer,
applyMiddleware(epicMiddleware)
);
epicMiddleware.run(rootEpic);
return store;
}
複製代碼
這個庫是將 NgRx 中的 action 和 reducer 工廠函數抽離出來的一個庫。
這個庫也提供了一個 createReducer 的函數,爲了搭配 createReducer,你可能須要簡單的改造一下你的 state。
// 爲 state 提供類型
interface PingPongState {
pingCount: number;
pongCount: number;
}
const initPingPongState: PingPongState = {
pingCount: 0,
pongCount: 0
}
const pingPongReducer = createReducer(
initState,
on(ping, state => ({...state, pingCount: state.pingCount + 1})),
on(pong, state => ({...state, pongCount: state.pongCount + 1}))
);
複製代碼
這種方式,比 Redux 經常使用建立 Reducer 的方式看起來更加的舒暢,它僅僅關注與數據的變化,而不是複雜的 switch 結構,這使得代碼更加的清晰。最重要的一點,createReducer
的返回值就是一個 reducer 函數,這也就意味着,你能夠直接使用 combineReducer
來作結合。
通常來講,action 是一個 plain object。爲了可以在使用 typescript 開發過程當中提供完整的類型支持,這個 action 工廠函數能夠提供明顯且有效的幫助。
const doPing = createAction('PING');
複製代碼
若是這個 action 須要傳入參數,那麼就可使用這種方式爲 action 提供類型檢測。
const fetchUser = createAction('FETCH_USER', props<{payload: string}>());
複製代碼
在這裏 props 僅僅返回了 undefined,不會形成任何影響,可是卻給 fetchUser 提供類型檢測的機會。fetchUser 的類型是一個函數(也就是所謂的高階 action),它接受一個參數,這個參數的類型來自於 props 中的泛型接口。那麼,當你須要調用這個 fetchUser 的時候,僅僅須要這樣。
store.dispatch(fetchUser({payload: 'Tony'}));
複製代碼
爲了能夠結合 RO,咱們須要使用這個操做符來強化類型推導,若是直接使用 RO 自帶的 ofType,那麼會有這種狀況發生。
const fetchUserEpic = action$ => action$.pipe(
ofType(fetchUser),
// 此處 typescript 會報錯,沒法轉發出正確的類型,致使 action.payload 失效。
mergeMap(action => {
return ajax.getJSON(`https://api.github.com/users/${action.payload}`));
}
map(response => fetchUserFulfilled(response)))
);
複製代碼
所以咱們須要使用這個 ofActionType 操做符,來將 ActionCreator 中的數據類型取出。
const fetchUserEpic = action$ => action$.pipe(
ofActionType(fetchUser),
mergeMap(action => {
return ajax.getJSON(`https://api.github.com/users/${action.payload}`));
}
map(response => fetchUserFulfilled(response)))
);
複製代碼
其實,我就是把 NgRx 中一些有用的地方應用到 Redux,主要就是解決了建立 action 類型安全的問題。本文參考以下。
Vuex、Flux、Redux、Redux-saga、Dva、MobX