Redux中间件原理与实现

Redux中间件

redux中间件类似Express的中间件,在Express中中间件运行于接收到request到返回response之间,redux中间件运行于action发出到reducer执行之间。他们的实现都是为了原本功能的增强。

实质

  1. 中间件的执行时机在aciton发出到reducer执行之间。

  2. 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

注意

  1. 上面store.dispatch = middleware(store))赋值中的middleware(store)都是在中间件中包装dispatch函数后的返回值。
  2. 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
}

上述中间件中三个参数分别用于:

  1. store 用于中间件获取getState等方法
  2. next 用于传递前一个中间件函数调用引用,注意:第一个是原生的store.dispatch
  3. 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.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值