redux middleWare原理及异步实战

Redux解决了react中出现的多交互、多数据源问题,但是如果有异步操作,或者要对操作进行拦截或者执行后回调就比较麻烦。于是我们需要Redux 中间件。

一、手动增强store.dispatch

我们知道 react-redux的 connect是一个高阶组件,它将组件包装之后拿到一个能够在组件中直接获取 context 的 state 的组件,并且用dispatch监听每一个action:

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
    class Connect extends Component {
       ...
       let dispatchProps = mapDispatchToProps
                ? mapDispatchToProps(store.dispatch, this.props)
                : {} // 用来 dispatch 的时候获取 store 的 dispatch
        ...    
        render() {
            return <WrappedComponent {...this.state.allProps}/>
        }
    }
    return Connect;
}

复制代码

如果要增强dispatch,我们可以对其进行重构,直接改写 store 实例中的 dispatch:

let store = createStore(rootReducer);
let dispatch = store.dispatch//拿到dispatch
store.dispatch = function (action) {//对dispatch进行重构
    console.log('旧状态',store.getState())//1.打印就状态
    dispatch(action)//2.在执行之前的action
    console.log('新状态',store.getState())//3.打印新状态
}
复制代码

以上的代码增强了dispatch方法,使执行顺序变成了action->log->reducer->log,执行结果如下:

二、使用redux的applyMiddleware方法增强store.dispatch

redux 提供了类似后端 Express 的中间件概念,本质的目的是提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。

官方说明如下:使用中间件扩展增强Redux store上的dispath 方法。因为中间件可能是异步的,所以这应该是定义在组合链中存储增强器。

redux applyMiddleware方法源码

export default function applyMiddleware(...middlewares) {//[middleware1,middleware2]
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))//将middleware放入链式数组
    dispatch = compose(...chain)(store.dispatch)//依次执行

    return {// 将增强过的dispatch返回
      ...store,
      dispatch
    }
  }
}
复制代码

applyMiddleware的使用方法,官方文档中给出两种用法:

const store = createStore(reducer, preloadedState, applyMiddleware(...))

const store = createStore(reducer, applyMiddleware(...))
复制代码

第二个参数初始化state不是必传的,源码中的createStore方法对参数进行了处理

三、middleware如何工作

redux createStore源码

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  ......    
    
}
复制代码

在createStore方法中判断参数并相应替换,最后createStore代码执行返回的是一个enhancer函数嵌套调用方法,也就是:

const store = applyMiddleware(...)(createStore)(reducer,preloadedState)
复制代码

如图所示:嵌套函数分别传入的createStore和reducer,创建了store,并定义了dispatch方法,并组合成obj传给了logger函数

logger中间件函数接受两个参数 dispatch getState(获取状态 派发动作) 并返回一个新的参数 next,形成了一个dispatch增强函数。小白我对于这个一长串的return理解成如下:

let logger1 = function({dispatch,getState}) {
    store.dispatch = function(action){
        console.log('旧状态1',getState())
        next(action)
        console.log('新状态1',getState())
    }
}
复制代码

这已经跟文章开头手动增强store.dispatch的函数十分相近了,主要区别在于next方法。

middleware 通过 next(action) 一层一层处理和传递 action 直到 redux 原生的 dispatch,这时next为客户端调用的dispatch方法,action为方法传入的actionType:{type:xxx,payload:xxx} 咳,代码要优雅,小白我理解了就要按照官方的来,正确的middleWare一样定义格式如下:

let logger = ({dispatch,getState}) => next => action =>{
    console.log('旧状态1',getState())
    next(action)//dispatch(action)
    console.log('新状态1',getState())
}
复制代码

中间件的实现和洋葱模型很像,先触发logger第一层,再触发dispatch事件,最后再从logger函数出来。

四、compose实现链式调用

实现多个中间件先后调用的关键是compose函数

redux compose源码链接

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
复制代码

源码很精简,理解有点复杂。其实是使用reduce不断将最右先调动函数的结果返回给后调用的函数。

举个栗子,

function add1(str){
    return str+1;
}
function add2(str){
    return str+2;
}
function add3(str){
    return str+3;
}

let add = compose(add3,add2,add1);
let r = add("啊哈哈哈")//啊哈哈哈123
复制代码

在这段代码中,compose函数执行顺序为add1->add2->add3,并将结果作为参数传给下一个函数。

在redux中当新 dispatch 执行时,[f1, f2, ... , fx, ..., fn],从右到左依次执行。

dispatch = f1(f2(f3(store.dispatch))))
复制代码

如图所示,从右至左执行logger2,logger1。logger2返回的代码作为参数传给logger1的next参数。按照图上1->2(执行下一个middleware)->3->4(触发redux 原生的 dispatch方法)->5->6 完成

链式middleware流程图如下

五、异步操作

很多时候,我们需要异步操作。用户触发第一个dispatch事件的action,需要发送第二个action。或者根据返回的根据发送第二个处理请求。

解决异步操作的方法:

(1)redux函数的参数是dispatch和getState,把返回的obj改成返回一个异步函数。

(2)异步操作结束之后,再发出一个 Action。

function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 1000);
  };
}
复制代码

这样子能理想得解决异步操作,而store.dispatch方法正常情况下,参数只能是对象,不能是函数。

这个时候可以引入redux-thunk

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

redux-thunk实现源码:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
复制代码

redux-thunk 做的事情就是判断 action 类型是否是函数,若是,则执行 action,若不是,则继续传递 action 到下个 middleware。

运用方法:

let store = createStore(
    rootReducer,
    applyMiddleware(thunk,logger1,logger2)
)
复制代码

如图所示,当store.dispatch运行到thunk中间件,发现返回的是一个function,则执行返回的函数,并返回,重新派发dispatch

因此使用redux-thunk,改造store.dispatch。可以实现异步方法

还有如 redux-promise redux-saga也可以解决异步的问题

猜你喜欢

转载自juejin.im/post/5b792b8a51882542e441f376