redux的中间件

一、什么是中间件?
  顾名思义就是在一个执行流程中间用的一个组件,截住过路的然后对其进行控制增强的操作来满足我们的需求。
 
    那redux为什么需要用中间件呢?我们看一下redux的数据流向
    view -> action -> reducer -> state ->view
    view触发事件触发action,action中根据type把数据传到对应的reduce中,然后reducer拿到数据返回新的state。
 
    这流程就像从view给出一个订单,然后货车马上拿着订单根据在action的这条路上不停的开到对应的reducer中,然后在reducer中卸货,对货物处理入库,然后车子再把处理后新的货品运回view中展示。当我们的操作都是同步的时候,这个流程毫无问题,但是当我们的需要异步获取数据来更新的时候就发现有问题了,即我们的车子已经出发了,但是要给reducer运的货还需要等一下才能到,等货备好了但车子已经跑没影了。
 
    那我们要怎么办呢加快备货吗,显然是想要加快异步操作是没有用的,毕竟车子一听到指令就开了,在开的同时备好货是来不及的,因为你的货在车外。而reducer是一个纯函数的数据处理点,是一个加工中心,人家哪里知道你的货是啥。所以最终我们要想办法拦住车子,等货备好装上车才让车子继续开往reducer,那我们就在action->reducer这个环节中间进行拦截住车子,在这个拦截住的过程中我们还可以对车子为所欲为了,比如日志控制,调用异步请求、路由控制等等。
    然后我们的流程就成为如下路径
    view -> action ->middlewire -> reducer -> state ->view
    举完这个例子应该也大概知道为什么redux处理异步数据需要用一个中间件了,那接下来我们来看看常用的redux中间件 
 
二、Redux-thunk
  npm安装该包,然后直接将redux-thunk引入到createStore操作的文件中
1 import { applyMiddleware, createStore } from 'redux';
2 import thunk from 'redux-thunk';
3  const store = createStore(
4   reducers, 
5   applyMiddleware(thunk)
6 );
   直接将thunk中间件引入,放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。即可以在reducer中进行一些异步的操作。 applyMiddleware() 其实applyMiddleware就是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。 中间件多了可以当做参数依次传进去。 
  redux-thunk源码:(路径node_modules/redux-thunk/src/index.js)   
 1 function createThunkMiddleware(extraArgument) {
 2      return ({ dispatch, getState }) => next => action => {
 3       if (typeof action === 'function') {
 4           return action(dispatch, getState, extraArgument);
 5       }
 6       return next(action);
 7      };
 8 } 
 9 const thunk = createThunkMiddleware();
10 thunk.withExtraArgument = createThunkMiddleware; 
11 export default thunk;

redux-thunk中间件export default的就是createThunkMiddleware()过的thunk,再看createThunkMiddleware这个函数,返回的是一个柯里化过的函数。我们再翻译成ES5的代码容易看一点

 1 function createThunkMiddleware(extraArgument) {
 2   return function({ dispatch, getState }) {
 3     return function(next){
 4       return function(action){
 5         if (typeof action === 'function') {
 6           return action(dispatch, getState, extraArgument);
 7         }
 8         return next(action);
 9       };
10     }
11   }
12 }

  可以看出来redux-thunk最重要的思想,就是可以接受一个返回函数的action creator。如果这个action creator 返回的是一个函数,就执行它,如果不是,就按照原来的next(action)执行。 正因为这个action creator可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。例如:

 1 export function addCount() {
 2   return {type: ADD_COUNT}
 3 } 
 4 export function addCountAsync() {
 5   return dispatch => {
 6     setTimeout( () => {
 7       dispatch(addCount())
 8     },2000)
 9   }
10 }

  addCountAsync函数就返回了一个函数,将dispatch作为函数的第一个参数传递进去,在函数内进行异步操作就可以了。当然为了避免在异步请求内去使用dispatch,以至于会出现回调地狱的情况,我们当然使用promise来处理,那就就得加上promiseMiddleware中间件来处理promise。为了能在更好的观察监控我们的action,可以用createLogger中间件来打印action日志。
 
  示例代码:
  store.js
 1 import { createStore, applyMiddleware } from 'redux';
 2 import thunkMiddleware from 'redux-thunk';
 3 import combineReducers from '../reducers/reducers';
 4 import promiseMiddleware from 'redux-promise'
 5 import { createLogger } from 'redux-logger';
 6 import createHistory from 'history/createBrowserHistory';
 7 const history = createHistory();
 8 const arr = [thunkMiddleware,promiseMiddleware];
 9 if (process.env.NODE_ENV !== 'pro') arr.push(createLogger({
10     diff: true,
11     collapsed: true,
12 }));
13 let store = createStore(combineReducers, applyMiddleware(...arr));
14 export default store;

  action.js

1 import types from '../store/types';
2 import {
3     get_user_info //题主是本地配的mock服务,也可以用一个定时器返回数据实现异步
4 } from '../api/userInfo'
5 export const getUserInfo = () => (dispatch) =>
6 dispatch({
7     type: types.GET_USER_INFO_REQUEST,
8     payload: Promise.resolve(get_user_info())
9 })

  reducer.js

 1 import types from '../store/types';
 2 import { reducerCreators } from '../util/index';
 3 const initState = {
 4     isLoading: false,
 5     userInfo: {},
 6     errorMsg: ''
 7 };
 8 
 9 export default reducerCreators(initState, {
10     [`${types.GET_USER_INFO_REQUEST}`]: (state, data) => {
11         return Object.assign({}, state, {
12             userInfo: data.data
13         })
14     }
15 })

   reducerCreators (../util/index) 重新生成reducer使其符合范式

 1 export function reducerCreators (initialState, actionTypeMapList) {
 2     return (state = initialState, action) => {
 3     const reducerInstance = typeof actionTypeMapList === 'object' &&             
 4         actionTypeMapList[action.type] ? 
 5         actionTypeMapList[action.type](state, action.payload ? 
 6         action.payload : {}, action.params) : 
 7         state;
 8     return reducerInstance;
 9     };
10 }

  view.js

 1 import React, { Component } from 'react';
 2 import { connect } from 'react-redux';
 3 import { bindActionCreators } from 'redux'
 4 import { getUserInfo } from 'actions/userInfo';
 5 class UserInfo extends Component {
 6     render() {
 7     const { userInfo, isLoading, errMsg } = this.props.userInfo;
 8         return (<div>
 9             {
10                 isLoading ? '请求中' :
11                 (errMsg ? errMsg :
12                     <div>
13                         <p>用户信息:</p>
14                         <p>用户名:{userInfo.name}</p>
15                         <p>介绍:{userInfo.intro}</p>
16                     </div>
17                 )
18             }
19             <button onClick={() => this.props.getUserInfo()}>请求用户信息</button>
20         </div>)
21     }
22 }
23 
24 const mapStateToProps = (state) => {
25     return {
26         userInfo: state.userInfo,
27     }
28 }
29 
30 const mapDispatchToProps = (dispatch) => {
31     return {
32         getUserInfo: bindActionCreators(getUserInfo, dispatch)
33     }
34 }
35 
36 export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);

三、Redux-saga

  redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
  可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。
  redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。(如果你还不熟悉的话,这里有一些介绍性的链接) 通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。(有点像 async/await,但 Generator 还有一些更棒而且我们也需要的功能)。
  你可能已经用了 redux-thunk 来处理数据的读取。不同于 redux thunk,你不会再遇到回调地狱了,你可以很容易地测试异步流程并保持你的 action 是干净的。
  示例
  store.js
 1 import { createStore, applyMiddleware } from 'redux';
 2 import combineReducers from '../reducers/reducers';
 3 import promiseMiddleware from 'redux-promise'
 4 import createSagaMiddleware from 'redux-saga'
 5 import mySaga from './saga'
 6 import { createLogger } from 'redux-logger';
 7 const sagaMiddleware = createSagaMiddleware(mySaga)
 8 const arr = [sagaMiddleware];
 9 arr.push(promiseMiddleware)
10 if (process.env.NODE_ENV !== 'pro') arr.push(createLogger({
11     diff: true,
12     collapsed: true,
13 }));
14 export const store = createStore(combineReducers, applyMiddleware(...arr));
15 sagaMiddleware.run(mySaga)

  这里saga也用npm安装一下,然后像和thunk一样的引入到store中,不同的是需要用createSagaMiddleware工厂函数创建一个Saga middleware,然后连接到store上,之后便使用sagaMiddleware.run(mySaga) 运行Saga。

  saga.js
 1 import { call, put, takeEvery, takeLatest, take } from 'redux-saga/effects'
 2 import types from '../store/types'
 3 function* fetchUser(action) {
 4     try {
 5         const data = yield action.payload
 6         yield put({ type: `${action.type}_SUCCESS`, payload: data })
 7     } catch (e) {
 8         yield put({ type: `${action.type}_FAILD`, message: e.message })
 9     }
10 }
11 function* mySaga() {
12     yield takeLatest(types['GET_USER_INFO'], fetchUser)
13 }
14 export default mySaga

  因为saga是实现为生成器函数(Generator functions),会yield对象到redux-saga middleware。被yield的对象都是一类指令,指令可以被middleware解释执行。当 middleware 取得一个 yield 后的 Promise,middleware 会暂停 Saga,直到 Promise 完成。在这里就是会等到action中传入的异步方法执行完毕后继续执行下一步。然后调用put指令发起一个action
  action.js
1 import types from '../store/types';
2 import {
3     get_user_info
4 } from '../api/userInfo'
5 export const getUserInfo = (params) =>
6 ({
7     type: types.GET_USER_INFO,
8     payload: get_user_info()
9 })

  向saga提供action的type和异步请求的结果

  reducer.js

 1 import types from '../store/types';
 2 import { reducerCreators } from '../util/index';
 3 const initState = {
 4 isLoading: false,
 5 userInfo: {},
 6 errorMsg: ''
 7 };
 8 export default reducerCreators(initState, {
 9     [`${types.GET_USER_INFO}_SUCCESS`]: (state, data) => {
10         return Object.assign({}, state, {
11             userInfo: data.data
12         })
13     }
14 })

  view.js

 1 import React, { Component } from 'react';
 2 import { connect } from 'react-redux';
 3 import { bindActionCreators } from 'redux'
 4 import { getUserInfo } from 'actions/userInfo';
 5 
 6 class UserInfo extends Component {
 7     render() {
 8     const { userInfo, isLoading, errMsg } = this.props.userInfo;
 9 
10         return (<div>
11             {
12                 isLoading ? '请求中' :
13                 (errMsg ? errMsg :
14                     <div>
15                         <p>用户信息:</p>
16                         <p>用户名:{userInfo.name}</p>
17                         <p>介绍:{userInfo.intro}</p>
18                     </div>
19                 )
20             }
21             <button onClick={() => this.props.getUserInfo()}>请求用户信息</button>
22         </div>)
23     }
24 }
25 
26 const mapStateToProps = (state) => {
27     return {
28         userInfo: state.userInfo,
29     }
30 }
31 
32 const mapDispatchToProps = (dispatch) => {
33     return {
34         getUserInfo: bindActionCreators(getUserInfo, dispatch)
35     }
36 }
37 
38 export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);

四、redux-thunk与redux-saga优缺点

Redux-thunk:
优点:
  • 库小量轻,代码简短
缺点:
  • thunk仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说thunk使得redux可以接受函数作为action,但是函数的内部可以多种多样
  • action的形式不统一
  • 异步操作分散,分散在各个action中 
 
 
Redux-saga
缺点:
  • 太复杂,学习成本较高
优点: 
  • 集中处理了所有的异步操作,异步接口部分一目了然
  • action是普通对象,这跟redux同步的action一模一样
  • 通过Effect,方便异步接口的测试
  • 通过worker 和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听
  • 异步操作的流程是可以控制的,可以随时取消相应的异步操作。 
 
(关于mobx的异步操作,主要使用了asyncawait和特有的runInAction,有兴趣的可以自行百度,很简便的用法)
原文地址:https://www.cnblogs.com/dfzc/p/11404983.html