深入浅出redux-middleware

栏目: IOS · Android · 发布时间: 5年前

内容简介:多数本来写这篇文章只是想写写

多数 redux 初学者都会使用 redux-thunk 这个中间件来处理异步请求(比如我)

本来写这篇文章只是想写写 redux-thunk ,然后发现还不够,就顺便把 middleware 给过了一遍。

为什么叫 thunk ?

thunk 是一种包裹一些稍后执行的表达式的函数。

redux-thunk 源码

所有的代码就只有15行,我说的是真的。。 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-middleware是个啥

深入浅出redux-middleware

上图描述了一个 redux 中简单的同步数据流动的场景,点击 button 后, dispatch 一个 action ,reducer 收到 action 后,更新 state 后告诉 UI ,帮我重新渲染一下。

redux-middleware 就是让我们在 dispatch action 之后,在 action 到达 reducer 之前,再做一点微小的工作,比如打印一下日志什么的。试想一下,如果不用 middleware 要怎么做,最 navie 的方法就是每次在调用 store.dispatch(action) 的时候,都 console.log 一下 actionnext State

store.dispatch(addTodo('Use Redux'));
复制代码
  • naive 的方法,唉,每次都写上吧
const action = addTodo('Use Redux');

console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
复制代码
  • 既然每次都差不多,那封装一下吧
function dispatchAndLog(store, action) {
  console.log('dispatching', action);
  store.dispatch(action);
  console.log('next state', store.getState());
}
复制代码
  • 现在问题来了,每次 dispatch 的时候都要 import 这个函数进来,有点麻烦是不是,那怎么办呢?

既然 dispatch 是逃不走的,那就在这里动下手脚, reduxstore 就是一个有几种方法的对象,那我们就简单修改一下 dispatch 方法。

const next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action); // 之前是 `dispatch(action)`
  console.log('next state', store.getState());
}
复制代码

这样一来我们无论在哪里 dispatch 一个 action ,都能实现想要的功能了,这就是中间件的雏形。

深入浅出redux-middleware
  • 现在问题又来了,大佬要让你加一个功能咋办?比如要异常处理一下

接下来就是怎么加入多个中间件了。

function patchStoreToAddLogging(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

function patchStoreToAddCrashReporting(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action)
    } catch (err) {
      console.error('Caught an exception!', err)
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      })
      throw err
    }
  }
}
复制代码

patchStoreToAddLoggingpatchStoreToAddCrashReportingdispatch 进行了重写,依次调用这个两个函数之后,就能实现打印日志和异常处理的功能。

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)
复制代码
  • 之前我们写了一个函数来代替了 store.dispatch 。如果直接返回一个新的 dispatch 函数呢?
function logger(store) {
  const next = store.dispatch

  // 之前:
  // store.dispatch = function dispatchAndLog(action) {

  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}
复制代码

这样写的话我们就需要让 store.dispatch 等于这个新返回的函数,再另外写一个函数,把上面两个 middleware 连接起来。

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  // Transform dispatch function with each middleware.
  middlewares.forEach(middleware => (store.dispatch = middleware(store)))
}
复制代码

middleware(store) 会返回一个新的函数,赋值给 store.dispatch ,下一个 middleware 就能拿到一个的结果。

接下来就可以这样使用了,是不是优雅了一些。

applyMiddlewareByMonkeypatching(store, [logger, crashReporter])
复制代码

我们为什么还要重写 dispatch 呢?当然啦,因为这样每个中间件都可以访问或者调用之前封装过的 store.dispatch ,不然下一个 middleware 就拿不到最新的 dispatch 了。

function logger(store) {
  // Must point to the function returned by the previous middleware:
  const next = store.dispatch

  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}
复制代码

连接 middleware 是很有必要的。

但是还有别的办法,通过柯里化的形式, middlewaredispatch 作为一个叫 next 的参数传入,而不是直接从 store 里拿。

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}
复制代码

柯里化就是把接受多个参数的函数编程接受一个单一参数(注意是单一参数)的函数,并返回接受余下的参数且返回一个新的函数。

举个例子:

const sum = (a, b, c) => a + b + c;

// Curring
const sum = a => b => c => a + b + c;
复制代码

ES6 的箭头函数,看起来更加舒服。

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}
复制代码

接下来我们就可以写一个 applyMiddleware 了。

// 注意:这是简单的实现
function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  let dispatch = store.dispatch
  middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
  return Object.assign({}, store, { dispatch })
}
复制代码

上面的方法,不用立刻对 store.dispatch 赋值,而是赋值给一个变量 dispatch ,通过 dispatch = middleware(store)(dispatch) 来连接。

现在来看下 reduxapplyMiddleware 是怎么实现的?

applyMiddleware

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */
 
 // 就是把上一个函数的返回结果作为下一个函数的参数传入, compose(f, g, h)和(...args) => f(g(h(...args)))等效

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)))
}
复制代码

compose 最后返回的也是一个函数,接收一个参数 args

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)
    }
    
    // 确保每个`middleware`都能访问到`getState`和`dispatch`
    
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // wrapDispatchToAddLogging(store)
    dispatch = compose(...chain)(store.dispatch)
    
    // wrapCrashReport(wrapDispatchToAddLogging(store.dispatch))

    return {
      ...store,
      dispatch
    }
  }
}

复制代码
深入浅出redux-middleware
借用一下大佬的图,

google搜索redux-middleware第一张

到这里我们来看一下 applyMiddleware 是怎样在 createStore 中实现的。

export default function createStore(reducer, preloadedState, enhancer){
  ...
}
复制代码

createStore 接受三个参数: reducer , initialState , enhancerenhancer 就是传入的 applyMiddleware 函数。

createStore-enhancer #53

//在enhancer有效的情况下,createStore会返回enhancer(createStore)(reducer, preloadedState)。
return enhancer(createStore)(reducer, preloadedState)
复制代码

我们来看下刚刚的 applyMiddleware ,是不是一下子明白了呢。

return createStore => (...args) => {
    // ....
}
复制代码

到这里应该就很容易理解 redux-thunk 的实现了,他做的事情就是判断 action 类型是否是函数,如果是就执行 action ,否则就继续传递 action 到下个 middleware

参考文档:


以上所述就是小编给大家介绍的《深入浅出redux-middleware》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Mathematica演示项目笔记

Mathematica演示项目笔记

吴飞 / 清华大学出版社 / 2010-7 / 39.00元

Mathematica是由美国科学家斯蒂芬·沃尔夫勒姆(Stephen Wolfram)领导的Wolfram Research Inc.研究公司所开发的一体化计算引擎。《Mathematica演示项目笔记》结合Mathematica演示项目以及第6版和第7版的最新功能(动态交互性、即时三维图形、数值模拟和仿真、高效实时计算、集成语言系统、多核并行计算和数字图像处理等),讲解了符号计算、程序设计、算......一起来看看 《Mathematica演示项目笔记》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具