• 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,有兴趣的可以自行百度,很简便的用法)
  • 相关阅读:
    WordCount项目基本功能
    让自己的头脑极度开放
    Docker安装Mysql5.7
    MySQL中的函数索引(Generated Column)及一次SQL优化
    关于老系统的重构和优化选择
    JIRA笔记(一):安装部署JIRA
    Jenkins 配置GitLab插件和Git插件
    Loading descriptor for XXX.'has encountered a problem' A internal error occured during:"Loading ....."
    Newton插值的C++实现
    Lagrange插值C++程序
  • 原文地址:https://www.cnblogs.com/dfzc/p/11404983.html
Copyright © 2020-2023  润新知