• Redux的中间件Middleware不难,我信了^_^


    Redux的action和reducer已经足够复杂了,现在还需要理解Redux的中间件。为什么Redux的存在有何意义?为什么Redux的中间件有这么多层的函数返回?Redux的中间件究竟是如何工作的?本文来给你解惑,Redux中间件从零到“放弃”。

    本文的参考网站只有二个,首当其冲的就是Redux的官方网站,本文的思考过程大多参考官方给出的例子。还有一个就是Redux的经典中间件,可以说Redux的中间件的产生就是为了实现它——redux-thunk

    写在前面:本文其实就是我理解Redux中间件的一个思考过程,中间不免来自我个人的吐槽,大家看看乐乐就好。

    我们为什么要用中间件?

    我们为什么要用中间件?这个问题提的好!为了回答这个问题,我现在提出一个需求,所有的store.dispatch都要监控dispatch之前和之后的state变化。那么我们会怎做呢?So easy,直接前后都加上console.log(store.getState())就可以了不是吗?

    console.log('dispatching', action)
    store.dispatch(getTodos({items:[]}))
    console.log('next state', store.getState())
    console.log('dispatching', action)
    store.dispatch(getTodos({items:["aaa"]}))
    console.log('next state', store.getState())
    

    没错,我们可以这么做。不过如果夸张点,我有成千上万的dispatch,那么console.log就要dispatch的数量*2了。然后当我们幸幸苦苦打完点,产品要上线了,我们需要把断点都关闭。这个时候难道我们要一个个去注释删除吗?

    不,我不干,这样可能还会改错。那么我们将此功能独立出来试试,这样不就可以实现复用了。将公用代码写入一个方法,然后变化的参数提取出来。

    function dispatchAndLog(store, action) {
        console.log('dispatching', action)
        store.dispatch(action)
        console.log('next state', store.getState())
    }
    dispatchAndLog(store, getTodos({items:[]}))
    dispatchAndLog(store, getTodos({items:["aaa"]}))
    

    这样是不是就方便了很多,注释的话只需要注释两行,而不是随着dispatch成倍数增长。但是我觉得这样写,对于其他合作的小伙伴不友好,相当于我自己写了一套语法出来。最好还是使用官方的store.dispatch的时候,自定义函数一起执行了。

    可以这样改写store.dispatch,将store.dispatch赋值给next,然后将diapatch变成我们自定义的函数,在这个自定义的函数中调用next,也就是原dispatch。这样就玩美地改写了dispatch,保留了原始功能,还添加了自定义的方法。

    const next = store.dispatch
    store.dispatch = function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
    

    锵锵锵~~~这个时候Redux中间件的雏形就出现了。

    MiddleWare就是对dispatch方法的一个改造,一个变异。

    多中间件的实现

    那么假象一下,我不仅需要监控state,我可能还有其他的功能。而且与监控state的方法相互独立。也就是我需要多个中间件,那么该如何实现呢?

    我们可以将每次的变异store.dispatch都传递给一个新的参数,传入下一次变异之中执行,但是要像这样next1next2……这样源源不断地下去吗?

    const next = store.dispatch
    const next1 = store.dispatch = function dispatchAndLog1(action) {
        console.log('dispatching', action)
        let result = next(action)
        console.log(result,'next state', store.getState())
        return result
    }
    const next2 = store.dispatch = function dispatchAndLog2(action) {
        console.log('dispatching1', action)
        let result = next1(action)
        console.log(result,'next state1', store.getState())
        return result
    }
    ...
    ...
    ...
    

    这样是不是格式有点丑?让我们想办法解放next参数。我的想法是这样的,先写一个compose,用来结合这些方法,然后返回一个变异的dispatch方法。

    const _dispatch=store.dispatch;
    function compose(){
        return function(action){
            _dispatch(action)
        }
    }
    store.dispatch=compose(dispatchAndLog1,dispatchAndLog2)
    

    嵌套函数的解放

    在实现compose方法之前我们先考虑一个问题,现在middlewares的结构是这样的,多层嵌套,一个函数嵌入一个函数,我们改如何将这个方法从嵌套中解放出来呢?

    function A(){
        function B(){
            function C(){
            }
        }
    }
    

    如何能避免面多层的嵌套?通过把函数赋值给一个参数,可以解放嵌套,但这样不太现实,因为我们需要创建许多的参数。

    const CM=function C(){}
    const BM=function B(){
        CM()
    }
    const AM=function A(){
        BM()
    }
    

    为了避免创建许多不必要的引用,我们可以用传递参数的方式来解决这个问题,直接将函数当作参数传入,那么就要注意一个问题,因为我们要先传入函数,但是不执行各函数,所以每个函数我们都要返回一个函数,也就是创建高阶函数,等都准备好了,从最外层的函数开始调用执行。

    function C(){
        return function(){}
    }
    function B(CM){
        return function(){
            CM()
        }
    }
    function A(BM){
        return function(){
            BM()
        }
    }
    

    这个方法执行的方式就很恶心,是一个函数嵌套后面的一个函数,将C返回的函数传入B,然后将B返回的函数传入A,最后执行(),逐层执行函数,这样也就没有逃离回调地狱。

    let compose=A(B(C()))
    compose()
    

    Array.reduce登场

    这个时候我们可以考虑下Array.reduce这个方法,将这些函数都合并起来。首先先创建一个数组,每个函数传递一个next的函数,以便于逐层执行函数。

    let array=Array(3)
    array[0]=function(next){
        return function(){
            let res= next();
            return res
        }
    }
    array[1]=function(next){
        return function(){
            let res= next();
            return res
        }
    }
    array[2]=function(next){
        return function(){
            let res= next();
            return res
        }
    }
    

    reduce只是合并,并不是执行,大家注意了,所以我们需要在每次执行之前加一层返回函数的操作。注意返回的函数需要和自定义函数的格式一致,也就是返回的函数需要传参next,相当于prevFunction是之前两个函数的结合,只有按照自定义函数的格式prevFunction才会有效。不然只有数组第一第二个会执行,因为初始值就是他们俩执行的结果返回。

    function dispatch(){
        console.log("dispatch")
        return "dispatch"
    }
    function compose(array){
        return array.reduce((prevFunction,currentFunction)=>{
            return function (next) {
                return prevFunction(currentFunction(next))
            }
        })
    }
    console.log(compose(array)(dispatch)());
    

    这里我定义了一个dispatch作为我的最初的next参数,传入中间件的集合之中,最先推入栈的函数,是最后执行的,因次我们的dispatch会在最后一层函数执行。细心如你们应该发现了。我的每个自定义函数都返回了上方next的返回值。其实就是为了将dispatch的值返回。这样compose函数执行之后所得到的值就是dispatch的值。这样我们就可以获取原版store.dispatch的值了。顺便科普下原版store.dispatch返回的值就是传入action

    根据上述思路,我们来写下合并中间件的compose函数,首先将store.dispatch_dispatch备用,然后compose这个高阶函数的第一层参数是中间件,第二层就是初始next函数,也就是原版的store.dispatch,我们传入副本_dispatch就可以了。最后改造store.dispatch

    const _dispatch=store.dispatch;
    function compose(){
        let middlewares=Array(arguments.length).join(",").split(",")
        middlewares=middlewares.map((i,index)=>{
            return arguments[index];
        })
        return middlewares.reduce((prevFunction,currentFunction)=>{
            return function (next) {
                return prevFunction(currentFunction(next))
            }
        })
    }
    store.dispatch=compose(dispatchAndLog1,dispatchAndLog2)(_dispatch)
    

    这样我们就可以调用多个中间件啦。

    融入createStore

    但是,官方的中间件可不是这么些的。我翻译了下官方对于应用中间件函数applyMiddleware()的一个定义,其实就是对createStore的一个增强enhance,也就是封装啦。但是有以下几点需要注意下:

    • 自定义中间件可以获取到createStoredispatch(action)getState()方法。
    • store.dispatch(action)执行时,中间件的链也会执行,也就是绑定的中间件都要执行。
    • 中间件只执行一次,并且作用于在createStore,而不是createStore返回的对象store。也就是说在store创建的时候,中间件已经执行完毕了。
    • applyMiddleware()要返回一个createStore,也就是经过改造之后的createStore

    那我们就根据以上的注意点,理解下官方设定的applyMiddleware()。 首先是如何增强 createStore,同时有保证原有功能?

    applyMiddleware()要返回一个createStore,也就是经过改造之后的createStore

    function applyMiddlewareTest(){
        return (createStore)=>{
            return function (reducer) {
                return createStore(reducer)
            }
        }
    }
    

    这样调用applyMiddlewareTest()(createStore)(reducer)不就等同于createStore(reducer)

    store.dispatch(action)执行时,中间件的链也会执行,也就是绑定的中间件都要执行。

    因为我们不会控制中间件的数量applyMiddlewareTest(m1,m2,m3……),所以我们采用arguments的特性,来获取中间件的数组,处理一下之后,调用我们已经写好的compose函合并一下,传给_dispatch,最后利用Object.assign拷贝store以及变异的dispatch

    function applyMiddlewareTest(){
        let middlewares=Array(arguments.length).join(",").split(",")
        middlewares=middlewares.map((i,index)=>{
            return arguments[index];
        })
        return (createStore)=>{
            return function (reducer) {
                let store = createStore(reducer)
                let _dispatch=compose(middlewares)(store.dispatch)
                return Object.assign({},store,{
                    dispatch:_dispatch
                })
            }
        }
    }
    

    自定义中间件可以获取到createStoredispatch(action)getState()方法。

    我们现在写的中间件是无法从函数内部中获取到dispatch(action)getState(),所以我们需要多写一层函数,传入dispatch(action)getState()。为了简洁,我们可以传入一个对象,包含了入dispatch(action)getState()两个方法

    function dispatchAndLog2({dispatch,getState}){
        return function (next){
            return function (action) {
                console.log('dispatching1', action)
                let result = next1(action)
                console.log(result,'next state1', store.getState())
                return result
            }
        }
    }
    

    这个函数可以简化为es6的写法:

    const dispatchAndLog2=({dispatch,getState})=>next=>action{
        ....
    }
    

    出现了!三层函数啊,第一层为了传递store的dispatch(action)getState()方法,第二层传递的参数next是下一个待执行的中间件,第三层是函数本体了,传递的参数action是为了最终传递给dispatch而存在的。

    回到applyMiddlewareTest,中间件中需要的dispatchgetState,我们可以加几行代码实现。直接执行中间件的第一层,将两个方法传递进去。此处需要注意dispatch因为我们需要传递的dispatch是变异之后的,而不是原生的。所以边我们改写下dispatch的方法,让中间件调用此方法时,是变异后的dispatch。不然中间件中执行的dispatch就无法执行中间件了。

    function applyMiddlewareTest(){
        ...
        let _dispatch=store.dispatch
        let _getState=store.getState
        let chain = middlewares.map(function (middleware) {
            return middleware({
                dispatch:function dispatch() {
                    return _dispatch.apply(undefined, arguments);
                },
                getState:_getState
            });
        });
        _dispatch=compose(chain)(store.dispatch)
        ....
    }
    

    redux-thunk的实现

    最后测试一波自己写的中间件是否成功:

    function logger({ getState }) {
        return function(next){
            return function(action){
                console.log('will dispatch', action)
                const returnValue = next(action)
                console.log('state after dispatch', getState())
                return returnValue
            }
        }
    }
    const ifActionIsFunction = {dispatch,getState} => next => action => {
        if (typeof action === 'function') {//如果是函数就执行并返回,然后再函数中执行dispatch,相当于延迟了dispatch。
            return action(dispatch, getState);
        }else{
            let res=next(action)
            return res
        }
    }
    let store=applyMiddlewareTest(logger,ifActionIsFunction)(createStore)(rootReducer)
    store.dispatch((dispatch,getState)=>{
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                dispatch(getTodos({items:["aaaa"]}))
                console.log(getState())
                resolve()
            },1000);
        })
    })
    

    运行是成功的,这里我写的中间件的功能是是如果action是函数,那么就返回函数的执行结果,并且向函数中传入dispatchgetState方法。这样就可以在action函数中调用dispatch了。机智如你一定发现了这个就是异步的一个实现,也就是redux-thunk的基本逻辑。(其实就是参照redux-thunk写的。)

    这里还有一个隐藏功能不知道大家发现了没有,我返回的是一个promise,也就是说我可以实现then的链式调用。

    store.dispatch((dispatch,getState)=>{
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                dispatch(getTodos({items:["aaaa"]}))
                console.log(getState())
                resolve("then方法调用成功了吗?")
            },1000);
        })
    }).then((data)=>{
        console.log(data)
    })
    
  • 相关阅读:
    关于JDK和eclipse的安装和汉化
    关于Android SDK Manager更新速度慢的解决方法
    Navicat Premium 11破解补丁下载及安装方法
    win8.1下无法运行vc++6.0的解决方法
    在Editplus中配置java的(带包)编译(javac)和运行(java)的方法
    关于在Editplus中设置内容提示比如syso的快捷输出的方法
    关于win8/win8.1系统不能调节亮度的解决办法
    JDK的安装和Java环境变量配置
    关于classpath
    Genymotion模拟器的安装及常见问题解决方法
  • 原文地址:https://www.cnblogs.com/cherryvenus/p/9685082.html
Copyright © 2020-2023  润新知