在 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 官网

相关文章
相关标签/搜索