zoukankan      html  css  js  c++  java
  • Redux学习之解读applyMiddleware源码深入middleware工作机制

    随笔前言

    上一周的学习中,我们熟悉了如何通过redux去管理数据,而在这一节中,我们将一起深入到redux的知识中学习。

    首先谈一谈为什么要用到middleware

    我们知道在一个简单的数据流场景中,点击一个button后,在回调中分发一个action,reducer收到action后就会更新state并通知view重新渲染,如下图所示

    但是如果需要打印每一个action来调试,就得去改dispatch或者reducer实现,使其具备打印功能,那么该如何做?因此,需要中间件的加入。

    上图展示了应用middleware后的Redux处理事件的逻辑,每个middleware都可以处理一个相对独立的事物,通过串联不同的middleware实现变化多样的功能!

    小结:Redux中的reducer更加的专注于转化逻辑,所以middleware是为了增强dispatch而出现的。

    middleware是如何工作的

    Redux提供了一个applyMiddleware方法来加载middleware,它的源码是这样的:

    import compose from './compose';
    
    export default function applyMiddleware(...middlewares) {
        return (next) => (reducer, initalState) => {
            let store = next(reducer, initalState);
            let dispatch = store.dispatch;
            let chain = [];
    
            var middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
            };
            chain = middlewares.map( middleware => middleware(middlewareAPI));
            dispatch = compose(...chain)(store.dispatch);
    
            return {
                ...store,
                dispatch
            };
    
        }
    
    }
    

    然后我们再上一个logger middleware的源码实现:

    export default store => next => action => {
        console.log('dispatch:', action);
        next(action);
        console.log('finish:', action);
    }
    
    

    虽然看到“源码”的那两个字的时候,内心一万只草什么马奔过,但是一看到代码这么精简,这么优美,那就初读一下源码把。
    然后

    接下来就开始解读上面源码

    深入解析middleware运行原理

    1. 函数式编程思想设计

    middleware是一个层层包裹的匿名函数,这其实是函数式编程的currying(Currying就是把一个带有多个参数的函数拆分成一系列带部分参数的函数)。那么applyMiddleware会对logger这个middleware进行层层的调用,动态的将store和next参数赋值。

    那么currying的middleware结构有什么好处呢?

    • 1.1 易串联: currying函数具有延迟执行的特性,通过不断currying形成的middleware可以积累参数,再配合组合(compose)的方式,这样很容易就形成pipeline来处理数据流
    • 1.2 共享store:在applyMiddleware执行的过程当中,store还是旧的,但是因为闭包的存在,applyMiddleware完成之后,所有的middleware内部拿到的store是最新的且是相同的。

    并且applyMiddleware的结构也是一个多层currying的函数,借助compose,applyMiddleware可以用来和其他插件加强createStore函数

    2. 给middleware分发store

    通过如下方式创建一个普通的store

        let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);
    

    上述代码执行完后,applyMiddleware方法陆续获得了3个参数,第一个是middlewares数组[mid1, mid2, mid3,...],第二个是Redux原生的createStore方法,最后一个是reducer。然后我们可以看到applyMiddleware利用createStore和reducer创建了一个store。而store的getState方法和dispatch方法又分别被直接和间接地赋值给middlewareAPI变量的store

        const middleAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middle => middleware(middlewareAPI))
    

    然后,每个middleware带着middlewareAPI这个参数分别执行一遍,执行后,得到一个chain数组[f1, f2, ..., fx, ..., fn],它保存的对象是第二个箭头函数返回的匿名函数。因为是闭包,每个匿名函数多可以访问相同的store,即middlewareAPI.

    3.组合串联middleware

    这一层只有一行代码,确是applyMiddleware精华所在。

        dispatch = compose(...chain)(store.dispatch);
    

    其中,compose是函数式编程中的组合,它将chain中的所有匿名函数[f1, f2, ..., fn]组装成一个新的函数,即新的dispatch。当新的dispatch执行的时候,[f1, f2, ...]会从右到左依次执行。Redux中compose的实现是这样的,当然实现的方式不唯一。

       function compose(...funs) {
           return arg => funcs.reduceRight( (compose, f) => f(composed), arg)
       }
    
    compose(...funcs)返回的是一个匿名函数,其中funcs就是chain数组。当调用reduceRight时,依次从funcs数组的右端取一个函数f(x)拿来执行,f(x)的参数composed就是前一次f(x+1)执行的结果,而第一次执行的f(n)n代表chain的长度,它的参数arg就是store.dispatch。
    

    因此,当compose执行完后,我们得到的dispatch是这样的:

    假设n=3:
    dispatch = f1(f2(f3(store.dispatch)));

    这时调用dispatch,每一个middleware就会依次执行了。

    4.在middleware中调用dispatch会发生什么呢?

    经过compose后,所有的middleware就算是已经串联起来了。

    那么问题来了?

    在分发store时,我们有说到每个middleware都可以访问store,也就是我们说的通过middlewareAPI这个变量去拿到dispatch属性进行操作。那么如果在middleware中调用store.dispatch会如何?和调用next()有什么区别?

    先上一波代码:

        //next()
        const logger = store => next => action => {
            console.log('dispatch:', action);
            next(action);
            console.log('finish:', action );
        }
        
        //store.dispatch(action);
        const logger = store => next => action {
            console.log('dispatch:', action);
            store.dispatch(action);
            console.log('finishL:', action);
        }
    

    在分发store的时候,我们有说过:midddleware中的store的dispatch通过匿名函数的方式和最终compose结束后的新dispatch保持一致,所以,在middleware中调用store.dispatch()和在其他任何地方调用其实效果是一样的。

    而如果在middleware中调用next(),效果是进入下一个middleware中。

    具体如下两个图1和图2所示:

    如图1所示,正常情况下,当我们去分发一个action时,middleware会通过next(action)一层层处理和传递action直到redux原生的dispatch。如果某个middleware中使用了store.dispatch(action)来分发action,就会发生如图2所示的情况。这就相当于是又从头开始了。

    那么问题又来了,假如这个middleware一直简单粗暴地调用store.dispatch(action),就会形成一个无限循环了,那么store.dispatch(action)的用武之地到底在哪里呢?

    假如我们需要发送一个异步请求到服务端获取数据,成功后弹出个message,这里我们通常会用到reduce-thunk这个插件。

    const thunk = store => next => action => 
        typeof action === 'function' ? 
            action(store.dispatch, store.getState) :
            next(action)
    

    代码很清晰,就是会先判断你dispatch过来的action是不是一个function,如果是则执行action,如果不是则传递到下一个middleware。因此,我们可以这样设计action:

        const getMessage = (dispatch, getState) => {
            const url = 'http://xxx.json';
            Axios.get(url)
              .then( res => {
                  dispatch({
                      type: 'SHOW_MESSAGE',
                      message: res.data.data
                  })
              })
              .catch( err => {
                  dispatch({
                      type: 'ERR_GET',
                      message: 'error'
                  })
              }) 
        }
    
    如上所示,只要在应用中去调用store.dispatch(getThenShow), redux-thunk这个middleware接收到后就会去执行getMessage方法。getMessage会先请求数据,根据请求结果去做相对应的分发action。这这里的dispatch就是通过redux-thunk这个middleware传递进来的。
    
    总结:在middleware中使用dispatch的场景一般是接受到一个定向action,而这个action又并不希望到达原生的分发action,往往用在异步请求的需求里面。
    

    ----------------作者的话:其实看了挺多遍,在脑海中构建整个middleware流程,再结合上周学习Redux时的Demo才渐渐的知其形,还需要多在实践中会其神!--------------

  • 相关阅读:
    【codecombat】 试玩全攻略 第九关 循环又循环
    【codecombat】 试玩全攻略 第十三关 已知敌人
    【codecombat】 试玩全攻略 第十一关 再次迷宫经历
    【codecombat】 试玩全攻略 第六关 cell commentary
    【codecombat】 试玩全攻略 第八关 火舞
    【codecombat】 试玩全攻略 第十二关 恐惧之门
    【codecombat】 试玩全攻略 第十四关 已知敌人
    苹果apns推送总结
    Xcode 提升速度小技巧
    UITextField 限制输入字数
  • 原文地址:https://www.cnblogs.com/fe-linjin/p/10494763.html
Copyright © 2011-2022 走看看