在 React 中使用 Redux-observable & Redux-rx-creator 來隔離反作用

在 React 中使用 Redux-observable & Redux-rx-creator 來隔離反作用

在開發 React 應用的過程當中,咱們通常習慣使用 Redux 做爲狀態管理工具,由於這個工具足夠的簡單。而在通常的項目中,Redux經過中間件,提供了足夠的能力處理同步異步事件。而處理異步事件的中間件有不少,Redux-observable 就是一個,它經過和 Rxjs 深度結合,提供了一種很棒的方式來處理異步事件以及反作用。react

Redux-observable(RO)

若是你瞭解過 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;
}

複製代碼

Redux-rx-creator

這個庫是將 NgRx 中的 action 和 reducer 工廠函數抽離出來的一個庫。

Reducer creator

這個庫也提供了一個 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 creator

通常來講,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'}));
複製代碼

ofActionType 操做符

爲了能夠結合 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

一篇文章總結redux、react-redux、redux-saga

ngrx 官網

Redux-observable 官網

相關文章
相關標籤/搜索