redux中middleware的作用及applyMiddleware其原理

Middleware的作用及applyMiddleware其原理

在redux中的Middleware一般被用于action 被发起之后,到达 reducer 之前的时刻,你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等

换句话说Middleware常常被用于封装store中的dispatch方法,从而得到一个新的dispatch方法

在react中使用middleware时,我们常常需要这样子注册,才能使用它

let todoApp = combineReducers(reducers)
let store = createStore(
  todoApp,
  applyMiddleware(
    rafScheduler,
    timeoutScheduler,
    thunk,
    vanillaPromise,
    readyStatePromise,
    logger,
    crashReporter
  )
)

Middleware的作用及applyMiddleware其原理

每一行代码都是为什么解决一个相应的需求,那对于Middleware,我们就从功能上来说明其作用和applyMiddleware原理实现

假设,你在创建一个 Todo 时这样调用:

store.dispatch(addTodo('Use Redux'))

问题

假如说我需要每一个 action 被发起以及每次新的 state 被计算完成时都将它们记录下来,这个时候你会怎么办???

问题实现 1

我相信很多人都是尝试在代码段中这样直接打印

let action = addTodo('Use Redux')

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

这样子确实可以实现相应功能

但很多时候我们的代码都追求可复用性和简洁性,而且我们实现的功能在实际开发中只会更为复杂

所以此时我们很多人可能也会想到,一个复用性更强的办法:封装Dispatch

问题实现 2

你把上面的代码抽出,并且实现Dispatch的封装

function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
}

然后替换

dispatchAndLog(store, addTodo('Use Redux'))

到此,你觉得还不错,但是我觉得,Dispatch的使用变得更为复杂了,我想要一种拓展性更强的实现方法

问题实现 3

我们可以尝试替换store中的Dispatch方法

因为store也只是一个普通的对象,只是他包含了很多方法

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

到此为此,我们的问题已经得到了一个很好的解决

但是!!!我们有的朋友已经发现了

如果我需要Dispatch附加的方法不止一个该怎么办

就是上面的实现方法不具有拓展性

问题实现 4

我们需要一个值的输入能够使其不断的被其他方法调用,并且返回最终结果时

最好的实现就是链式调用

同时Middleware的可拓展性也可以得到大大增强

我们先修改一下上面的函数

隐藏Monkeypatching

function logger(store) {
  // 这里的 next 必须指向前一个 middleware 返回的函数:
  let next = store.dispatch

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

我们可以在 Redux 内部提供一个可以将实际的 monkeypatching 应用到 store.dispatch 中的辅助方法:

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

  // 在每一个 middleware 中变换 dispatch 方法。
  middlewares.forEach(middleware =>
    store.dispatch = middleware(store)
  )
}

然后像这样应用多个 middleware:

applyMiddlewareByMonkeypatching(store, [ logger, crashReporter ])

我们需要Middleware每次执行的时候dispatch都是上一个Middleware执行完的返回结果

所以我们上面的函数代码实现显然是需要修改的

如果 applyMiddlewareByMonkeypatching 方法中没有在第一个 middleware 执行时立即替换掉 store.dispatch,那么 store.dispatch 将会一直指向原始的 dispatch 方法。也就是说,第二个 middleware 依旧会作用在原始的 dispatch 方法。

但是,还有另一种方式来实现这种链式调用的效果。可以让 middleware 以方法参数的形式接收一个 next() 方法,而不是通过 store 的实例去获取

我们使用函数的柯里化来实现优美的函数

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

这正是 Redux middleware 的样子。

Middleware 接收了一个 next() 的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的 next(),以此类推。由于 store 中类似 getState() 的方法依旧非常有用,我们将 store 作为顶层的参数,使得它可以在所有 middleware 中被使用。

我们可以写一个 applyMiddleware() 方法替换掉原来的 applyMiddlewareByMonkeypatching()

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

  let dispatch = store.dispatch
  middlewares.forEach(middleware =>
    dispatch = middleware(store)(dispatch)      //接收上一个函数处理后的dispatch
  )

  return Object.assign({}, store, { dispatch })
}

这与 Redux 中 applyMiddleware() 的实现已经很接近了

问题实现 5 :最终的方法

这是我们刚刚所写的 middleware:

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

然后是将它们引用到 Redux store 中:

import { createStore, combineReducers, applyMiddleware } from 'redux'

let todoApp = combineReducers(reducers)
let store = createStore(
  todoApp,
  // applyMiddleware() 告诉 createStore() 如何处理中间件
  applyMiddleware(logger, crashReporter)
)

就是这样!现在任何被发送到 store 的 action 都会经过 loggercrashReporter

// 将经过 logger 和 crashReporter 两个 middleware!
store.dispatch(addTodo('Use Redux'))

至此我们可以很清楚地看到Middleware和applyMiddleware的实质

  • Middleware实质上就是对于dispatch的封装
  • 而applyMiddleware是实现Middleware的链式调用,告诉store它的dispatch该怎么样使用

如果他对你有帮助请帮忙点一下赞吧~

原文链接:https://juejin.cn/post/7325269225430515727 作者:悲伤周杰伦

(0)
上一篇 2024年1月19日 上午10:00
下一篇 2024年1月19日 上午10:11

相关推荐

发表回复

登录后才能评论