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该怎么样使用

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

相关推荐
程序员鱼皮4 分钟前
我花 300 块,让 Claude Fable 5 开发桌面 APP,值么?
前端
William_Xu10 分钟前
JavaScript 并发控制
前端
拾年27510 分钟前
从零手写 Ajax:用原生 XHR 搭建前后端交互全流程
前端·javascript·ajax
光影少年11 分钟前
懒加载与分包:React.lazy + Suspense
前端·react.js·掘金·金石计划
拉勾科研工作室25 分钟前
区块链工程毕业论文题目【249个】
开发语言·javascript
小林ixn27 分钟前
你以为你懂 + 号?看完这篇 Bun + TS 实战,才发现以前全写错了
前端·javascript·typescript
namexingyun1 小时前
开源前端生态如何成为 AI UI 生成的“燃料“:shadcn/ui、Tailwind CSS、Storybook 技术价值全解剖
java·前端·人工智能·python·ui·开源·ai编程
Zyed1 小时前
[STM32]Day15读写FLASH+读取ID
前端·stm32·性能优化
jvxiao2 小时前
你真的懂作用域吗?从编译原理角度深度 JS 的作用域
前端·javascript
Darling噜啦啦2 小时前
二叉树与递归算法实战:从树结构到 LeetCode 爬楼梯,一文吃透前端数据结构与递归思维
前端·javascript·数据结构