Redux中间件
redux中间件类似Express的中间件,在Express中中间件运行于接收到request到返回response之间,redux中间件运行于action发出到reducer执行之间。他们的实现都是为了原本功能的增强。
实质
-
中间件的执行时机在aciton发出到reducer执行之间。
-
redux中间件的实质是对redux中store.dispath函数的再封装,
当中间件被rudex初始化完成后,每次调用store.dispatch都会执行一遍每个中间件,他们之间是链式关系,组织方式类似于C语言的函数指针链表,每个中间件执行本身功能且"复写"store.dispath后之将自身的返回作为参数传递到另一个中间件中,形成调用链。
表现形式
中间件实现的目的是以其不存在的形式实现附加的功能,不存在的形式是指调用他的功能时和没有这个功能时相同,对使用者不可见,比如假设有一个logger中间件,能够实现打印action类型和内容的功能,原生通过redux在action触发后,利用dispath函数传入action作为参数调用对应的reducer:
store.disptah({"ADD_ITEM","eat apple"})
在引入日志记录中间件logger后,调用方式仍然相同,但是却能实现日志记录功能
store.disptah({"ADD_ITEM","eat apple"})
//输出:
action.type: "ADD_ITEM"
action.content: "eat apple"
也就是说中间节的存在感不强,在增加功能的同时,保持原生的使用方式(有些中间节会有附加方法)。
具体实现
以在TodoList构建aciton记录和执行时间两个中间件来探索其具体实现。
可以这样:
//原生
store.disptah({"ADD_ITEM","call girl"})
//增加action记录和执行时间记录
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
console.log("执行之间是:"+ new Date().getTime()) //执行时间
store.disptah({"ADD_ITEM","call girl"})
//输出
但是这样多了代码,当想要保持原有调用且实现上述功能,需要进一步封装
function actionAndTimeLogger(store,action){
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
console.log("执行之间是:"+ new Date().getTime()) //执行时间
store.disptah(action)
}
//调用
actionAndTimeLogger(store,{"ADD_ITEM","call girl"})
封装函数后,调用比较简单,但是却无法将两个功能拆开了,比如当前只需要时间记录不需要action记录,拆分为两个函数可以实现
function actionLogger(store,action){
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
store.disptah(action)
}
function timeLogger(store,action){
console.log("执行之间是:"+ new Date().getTime()) //执行时间
store.disptah(action)
}
//调用
timeLogger(store,{"ADD_ITEM","call girl"})
这样虽然能够解决问题,但是同时出现了新的问题,当我两个功能都需要时,一旦调用actionLogger和timeLogger函数就会执行两次dispath(action),与预期想要功能不符。到此为止,我不知道怎么办了。。。。
依照官网教程,进一步,通过重写dispath函数来实现,目的实现链式调用(思路真清奇)
function actionLogger(store,action){
let next = store.dispatch
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
let result = next(action)
return result //这里的result就是包装后的store.dispath
}
function timeLogger(store,action){
let next = store.dispatch
console.log("执行之间是:"+ new Date().getTime()) //执行时间
let result = next(action)
return result //这里的result也是包装后的store.dispath
}
到上一步,调用结果任然不对,需要将diapath延迟执行,如果闭包来实现
function addActionLogger(store){
let next = store.dispatch
store.dispatch = function actionLogger(action){ //重写store.dispatch
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
let result = next(action)
return result
}
}
function addTimeLogger(store){
let next = store.dispatch
store.dispatch = function timeLogger(action){
console.log("执行之间是:"+ new Date().getTime()) //执行时间
let result = next(action)
return result
}
}
这是可以这样调用实现功能
addTimeLoggeraddActionLogger(store)
addTimeLogger(store)
store.dispatch({"ADD_ITEM","call girl"}) //实际执行了addTimeLogger->actionLogger->原生dispatch
上述能够实现指定目的的原因是当执行addActionLogger函数时重写了原生store.dispatch函数为actionLogger,执行addTimeLogger函数又将actionLogger重写为timeLogger,到这里调用store.dispatch等同于调用timeLogger(action),实现最原始屏蔽细节实现增加附属功能的功能。
在进一步,可以dispatch的复写从中间件的实现抽离出来,利用辅助函数来实现
function addActionLogger(store){
let next = store.dispatch
return function actionLogger(action){ //区别在这里,直接返回新的包装后的dispatch而不重写
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
let result = next(action)
return result
}
}
function addTimeLogger(store){
let next = store.dispatch
return function timeLogger(action){ //区别在这里,直接返回新的包装后的dispatch而不重写
console.log("执行之间是:"+ new Date().getTime()) //执行时间
let result = next(action)
return result
}
}
//工具函数
function applyMiddle(store,middlewareArrays){
middlewareArrays = middlewareArrays.slice()
middlewareArrays.reverse()
// 在每一个 middleware 中变换 dispatch 方法。
middlewareArrays.forEach(middleware => (store.dispatch = middleware(store)))
}
//调用
applyMiddle(store,[addActionLogger,addTimeLogger])
store.dispatch({"ADD_ITEM","call girl"})//链式调用addActionLogger->addTimeLogger->原生dispatch
注意
- 上面store.dispatch = middleware(store))赋值中的middleware(store)都是在中间件中包装dispatch函数后的返回值。
- foreach中的每迭代一次,store.dispatch就会被中间件重写一变,后一个中间件中next函数可以操作前一个中间件,实现链式调用。apb
上述能够实现原始需求,链式连接,如果函数指针形成的链表,调用后一个中间件就可以自动调用前一个中间节。还有一种不是依靠store的实例来传递dispatch函数来实现,而是通过函数参数来传递中间件到中间件的next()方法来实现,
function actionMiddle(store){
return function addActionLogger(next){//next就是要传递的中间件函数
return function actionLogger(action){ //区别在这里,直接返回新的包装后的dispatch而不重写
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
let result = next(action)
return result
}
}
}
function timeMiddle(store){
function addTimeLogger(next){//next就是要传递的中间件函数
return function timeLogger(action){ //区别在这里,直接返回新的包装后的dispatch而不重写
console.log("执行之间是:"+ new Date().getTime()) //执行时间
let result = next(action)
return result
}
}
}
//工具函数
function applyMiddle(store,middlewareArrays){
middlewareArrays = middlewareArrays.slice()
middlewareArrays.reverse()
let dispatch = store.dispatch//原生dispatch方法
// 在每一个 middleware 中通过 函数形参next 拿到上一个中间件的函数应用,供本身调用。
middlewareArrays.forEach(middleware => (dispatch = middleware(store)(dispatch))
}
//调用
applyMiddle(store,[addActionLogger,addTimeLogger])
store.dispatch({"ADD_ITEM","call girl"})//链式调用addActionLogger->addTimeLogger->原生dispatch
到这一步,完成了互不影响、可单独使用、隐藏细节的中间件,这也就是网上所说“中间件是一个参数分别为store、next、action、的三级函数”,实际上next除第一次是原生的dispatch函数,其余都是中间件对dispatch的再封装。为简化书写,用es6实现柯里化,上述两个中间件函数可写为
function actionMiddle = store => next => action => {
console.log(`action type: ${action.type}, action content: ${action.content}`)//action记录
let result = next(action)
return result
}
function timeMiddle = store => next => action =>
console.log("执行之间是:"+ new Date().getTime()) //执行时间
let result = next(action)
return result
}
上述中间件中三个参数分别用于:
- store 用于中间件获取getState等方法
- next 用于传递前一个中间件函数调用引用,注意:第一个是原生的store.dispatch
- action 用于执行最后一步要执行的reducer
最终正式的调用
import { createStore, applyMiddleware } from "redux"
const store = createStore(
reducer,
applyMiddleware
)
//正式调用,会执行acitonLogger和timelogger 两个中间件
store.dispatch({"ADD_ITEM","call girl"})
补充知识
柯里化
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
参考文献
redux 官网教程: https://cn.redux.js.org/docs/advanced/Middleware.html.