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 都会经过 logger
和 crashReporter
:
// 将经过 logger 和 crashReporter 两个 middleware!
store.dispatch(addTodo('Use Redux'))
至此我们可以很清楚地看到Middleware和applyMiddleware的实质
- Middleware实质上就是对于dispatch的封装
- 而applyMiddleware是实现Middleware的链式调用,告诉store它的dispatch该怎么样使用
如果他对你有帮助请帮忙点一下赞吧~
原文链接:https://juejin.cn/post/7325269225430515727 作者:悲伤周杰伦