redux中middleware的作用及applyMiddleware其原理

Middleware的作用及applyMiddleware其原理

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

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

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

jsx 复制代码
let todoApp = combineReducers(reducers)
let store = createStore(
  todoApp,
  applyMiddleware(
    rafScheduler,
    timeoutScheduler,
    thunk,
    vanillaPromise,
    readyStatePromise,
    logger,
    crashReporter
  )
)

Middleware的作用及applyMiddleware其原理

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

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

jsx 复制代码
store.dispatch(addTodo('Use Redux'))

问题

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

问题实现 1

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

js 复制代码
let action = addTodo('Use Redux')

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

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

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

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

问题实现 2

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

js 复制代码
function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
}

然后替换

js 复制代码
dispatchAndLog(store, addTodo('Use Redux'))

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

问题实现 3

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

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

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

js 复制代码
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 中的辅助方法:

js 复制代码
function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

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

然后像这样应用多个 middleware:

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

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

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

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

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

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

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

js 复制代码
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:

js 复制代码
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 中:

js 复制代码
import { createStore, combineReducers, applyMiddleware } from 'redux'

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

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

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

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

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

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

相关推荐
冴羽8 分钟前
SvelteKit 最新中文文档教程(18)—— 浅层路由和 Packaging
前端·javascript·svelte
哀木22 分钟前
聊聊前端埋点 clarity:我会一直视监你... 永远...
前端
墨渊君26 分钟前
Expo 入门指南:让 React Native 开发更轻松(含环境搭建)
前端·javascript·react native
xnian_33 分钟前
策略模式实际用处,改吧改吧直接用,两种方式
java·服务器·前端
jane_xing36 分钟前
Next.js + SQLite 项目 Docker 生产环境部署方案
javascript·docker·sqlite
31535669131 小时前
一文带你了解二维码扫码的全部用途
前端·后端
七月shi人1 小时前
用claude3.7,不到1天写了一个工具小程序(11个工具6个游戏)
前端·小程序·ai编程
Billy Qin1 小时前
Rollup详解
前端·javascript·rollup
夜寒花碎1 小时前
前端自动化测试一jest基础使用
前端·单元测试·jest
小徐_23332 小时前
uni-app工程实战:基于vue-i18n和i18n-ally的国际化方案
前端·微信小程序·uni-app