redux中间件applyMiddleware

Middleware是什么

Middleware 是包装了 store 的 dispatch 方法。技术上讲,任何 middleware 能做的事情,都可能通过手动包装 dispatch 调用来实现,但是放在同一个地方统一管理会使整个项目的扩展变的容易得多。

重新包装 dispatch 方法

为什么要重新包装 dispatch

中间件的作用可以让我们决定什么时候调用dispatch,可能在promise函数执行完或在action里面执行回调函数后,这就需要对旧的dispatch函数进行重新包装,让它能够先执行中间件函数里面的方法,并把真正的dispatch传递給中间件函数:

let storeDispatch = stroe.dispatch;// 取出store的dispatch方法保存

// 重写dispatch方法
stroe.dispatch = function (action) {
  console.log('before dispatch')
  stroe.dispatch(action) // 在适当的时候调用真实的dispatch方法
  console.log('after dispatch')
}

只有一个中间件

logger中间件

我们可以写一个logger中间件来进一步了解:

function logger(store){
  return (dispatch)=>{
    return (action)=>{
        console.log("before logger");
        store.dispatch(action);
        console.log('after logger')
    }
  }
}

中间件是一个柯里化组合的函数,每个层级都包装有对应的函数参数供我们调用,真正执行的dispatch方法其实在最后一个返回的函数里面。

applyMiddleware

applyMiddleware函数如下:

function applyMiddleware(middlewares) { // @params 中间件数组
  return function (createStore) { // @params 创建store函数
    return function (reudcer) { // @params reducer
    	return store; // @return 返回 createStore(reducer)
    }
  }
}

applyMiddleware也是一个柯里化组合的函数,不过最终返回的是一个store,照上面说的,redux中间件处理的是dispatch方法,这里也把storedispatch方法重新包装一下:

function applyMiddleware(middleware) {
  return function (createStore) {
    return function (reudcer) {
      const store = createStore(reudcer);
      const dispatch = ()=> throw new Error("dispatch 还不能用,还没改造成next方法");
      dispatch = middleware(store)(store.dispatch); // 改造包装后的dispatch; 对应logger(store)(dispatch)
      return {
        ...store,
        dispatch
      }
    }
  }
}

// 根据applyMiddleware需要返回的函数传入对应的值
applyMiddleware(logger)(createStore)(reducer);

这样每次在组件中调用storedispatch方法时,其实调用的是经过中间件logger包装后的dispatch

function dispatch(action){
  console.log('before logger')
  store.dispatch(action) // 这里才是真实调用store.dispatch;
  console.log('after logger')
}

多个中间件

多个中间件的情况比较复杂,需要保证每个中间件方法都能执行,并且能够像洋葱模型一样,dispatch方法能够在最里面执行:

function applyMiddleware(...middlewares) {
  return function (createStore) {
    return function (reudcer) {
      const store = createStore(reudcer)
      const middlewareAPI = {
        getState: store.getState,
        dispatch: (...args) => dispatch(...args),
      }
      
      // 将中间件函数执行,传递`getState`和`dispatch`方法并返回
      const chain = middlewares.map((middleware) => middleware(middlewareAPI))
      
      // compose函数会将包含了dispatch方法的中间件数组组合成一个嵌套的包装函数返回
      const dispatch = compose(...chain)(store.dispatch)
      
      return {
        ...store,
        dispatch,
      }
    }
  }
}

这里compose方法把中间件里面的dispatch方法给包装到了一起,让中间件能够一层层往里执行:

//compose
function compose(...funcs) {
  if (funcs.length === 0) {
    return (args) => args
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, next) => (...args) => a(next(...args)))
}

这里的reduce不好理解,把它拆成函数,可能会好理解一点:

function compose(...middlewarw) {
  return function(storeDispatch) {

    function dispatch(index, action) {
      let fn = middlewarw[index]
      
      // 回调里面的 next 方法,执行的是下一个中间件函数
      let next = () => dispatch(index + 1, action)
      // 如果还有下一个中间件就继续执行,并把action传进去,否则执行store.dispatch方法
      fn ? fn(next)(action) : storeDispatch(action)
    }

    return (action) => dispatch(0, action)
  }
};

常用中间件

另外常用到的中间件,这里也写下它的源码实现:

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-promise

function isPromise(obj) {
    return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
    return next => action => {
        return isPromise(action.payload)
            ? action.payload
                .then(result => dispatch({ ...action, payload: result }))
                .catch(error => {
                    dispatch({ ...action, payload: error, error: true });
                    return Promise.reject(error);
                })
            : next(action);
    };
}