Redux源码系列(二)出彩的设计:中间件

405 阅读2分钟

中间件可以说是 Redux 最出色的设计之一了,通过他,我们可以实现很多有用的功能,比如记录日志、记录action历史、实现撤销操作等等。废话不多说,直接操作,在上一节代码的基础上,实现记录日志和识别 Promise 的中间件,先抛开 Redux 实现几个简单版本的中间件

记录日志

创建一个 addLoggingToDispatch,该方法有接收创建的 store 返回一个新的 dispatch,实现步骤:

  • 记录原来 store.dispatch;
  • 在新返回的函数中打印 state 的变化,并调用原 dispatch 返回值;

代码

const addLoggingToDispatch = (store) => {
  const originDispatch = store.dispatch;
  return (action) => {
    console.group(action.type)
    console.log('start', store.getState());
    const result = originDispatch(action);
    console.log('end', store.getState());
    console.groupEnd(action.type)
    return action;
  }
}

使用

store.dispatch = addLoggingToDispatch(store);
store.dispatch(action);

识别 Primise

创建 addPromiseSupportToDispatch,判断传入的 action 是否为 Promise,是的话将在 then 中处理 dispatch;

const addPromiseSupportToDispatch = (store) => {
  const originDispatch = store.dispatch;
  return (action) => {
    if (action instanceof Promise) {
      action.then(originDispatch);
    } else {
      originDispatch(action);
      return action;
    }
  }
}

使用

store.dispatch = addPromiseSupportToDispatch(store);
store.dispatch(Promise.resolve(action))

要两个结合使用的话就得注意赋值顺序了,logger 中间件接收到的action 应该是 promise 中间件处理过的action

store.dispatch = addLoggingToDispatch(store);
store.dispatch = addPromiseSupportToDispatch(store);

Redux解决方案:applyMiddleware

上面两个中间件为了方便,并没有按照 Redux 的标准写,现在我们来改装下:

const addPromiseSupportToDispatch = (store) => (next) => (action) => {
  if (action instanceof Promise) {
    action.then(next);
  } else {
    next(action);
    return action;
  }
}

const addLoggingToDispatch = (store) => (next) => (action) => {
  console.group(action.type)
  console.log('start', store.getState());
  const result = next(action);
  console.log('end', store.getState());
  console.groupEnd(action.type)
  return result;
}

next 表示 dispatch

createStore 其实是能接受三个参数

  • reducer
  • initialState 初始化值
  • enhancer applyMiddleware 返回的函数 在源码中有这么一段代码

这里可以看出 applyMiddleware 的返回值是一个两级柯里化的函数,applyMiddleware 接受若干个中间件

export default function applyMiddleware(...middlewares) {
  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))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware 承担了创建 store,并将 storedispatch 分发给各个中间件

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)
}

这里包装这个 middlewareAPI,并替换掉 storedispatch,目的是为了在中间件中禁止调用 dispatch,至于为什么禁止这里已经说明,你直接调用的 dispatch 是没应用到别的中间件的 dispatch

compose(...chain)(store.dispatch)

这个就是典型的函数式编程里面的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)))
}

最后将我们写的两个中间应用到store中

const store = createStore(
  rootReducer,
  {
    name: 'jack',
    age: 18,
  },
  applyMiddleware(addPromiseSupportToDispatch, addLoggingToDispatch),
);

总结

感谢观看,感觉写的有点乱,归纳不足处感谢指正

applyMiddleware源码地址

文中示例代码目录地址:github.com/Yang-yu-don…

系列文章

Redux源码系列(一)从一个store开始

Redux源码系列(二)出彩的设计:中间件

参考

《React状态管理与同构实战》