美文网首页
redux applyMiddleware 中间件原理

redux applyMiddleware 中间件原理

作者: 前端劝退师_ty | 来源:发表于2019-01-17 11:47 被阅读0次

    记录下自己对redux 中间件的理解,希望可以帮助到你。

    applyMiddleware中间件在哪个部分运行

    中间件 听名字就能感觉出来 肯定是运行在程序中间

    重点是运行在哪个部分中间

    正常流程这样子

    这个一个正常的流程

    中间件允许我们在dispatch action 之后 到达reducer之前 这个区间 做自己想做的事情(为所欲为≧ω≦)

    加入了中间件这样子

    现在搞明白了他的运行环境,那这个东西到底能做些什么,网上有很多例子 打印啊 报错日志啊 这些都没用过(允许我皮一下),不过异步请求是我们在项目中实实在在需要用到的

    使用场景 发起一个action 自动ajax请求 请求成功后发出reducer 更改state 触发更新view 这是一个异步请求的过程

    然后我们来结合源码弄清楚他的运行原理 最后再说怎么实现异步请求。

    看一下实际项目中运行的代码

    
    import { createStore, applyMiddleware, compose } from 'redux';
    
    import rootReducer from' ../reducers';
    
    import DevTools from' ../containers/DevTools';
    
    import thunk from 'redux-thunk';
    
    finalCreateStore =compose(
    //这是实际项目中用到的场景(拿项目说话,有理有据的样子)
    applyMiddleware(thunk, api, reduxRouterMiddleware),
    //applyMiddleware里面三个参数 thunk是redux-thunk提供  api 自己封装  
    //reduxRouterMiddleware可以把router中的location(url信息等)注入到redux中
     DevTools.instrument(),
    //这个是react调试用的
    persistState(getDebugSessionKey())
    //这个是react增强调试 切换路由状态将会被清空重置
    //主要介绍和中间件相关的  这些无关的简要略过
    )(createStore);
    const store = finalCreateStore(rootReducer, initialState);
    

    可以看到实际项目中应用的场景是这样子
    重新整理下代码 屏蔽那些 本轮不看的代码

    import { createStore, applyMiddleware, compose } from 'redux';
    
    import rootReducer from' ../reducers';
    const store = compose(
      applyMiddleware(d1, d2, d3),
    )(createStore)(rootReducer, initialState);
    

    是的 现在这样是 咱们要说的主要代码
    一层层看 compose先是运行一次传递createStore作为参数 内部返回的代码又运行 传递了rootReducer 和 initialState 作为参数
    那现在我们来看看 compose 内部执行了什么

    /**
     * Composes single-argument functions from right to left. The rightmost
     * function can take multiple arguments as it provides the signature for
     * the resulting composite function.
     *
     * @param {...Function} funcs The functions to compose.
     * @returns {Function} A function obtained by composing the argument functions
     * from right to left. For example, compose(f, g, h) is identical to doing
     * (...args) => f(g(h(...args))).
     */
    //在这里其实已经可以看到 compose(f, g, h)  执行结果就是  (...args) => f(g(h(...args)))
    function compose() {
      for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
        funcs[_key] = arguments[_key];
      }
    
      if (funcs.length === 0) {
        return function (arg) {
          return arg;
        };
      }
    
      if (funcs.length === 1) {
        return funcs[0];
      }
    
      return funcs.reduce(function (a, b) {
        return function () {
          return a(b.apply(undefined, arguments));
        };
      });
    }
    

    compose代码是我现在项目中用的,直接到node_modules中翻出来的
    可能和以前版本不一样 网上很多文章 内部代码是用 reduceRight 不管啦以我自己翻到的为主(就是这么霸气 ╮( ̄▽  ̄)╭)

    funcs.reduce(function (a, b) {
        return function () {
          return a(b.apply(undefined, arguments));
        };
      });
    主要的代码可以看见 就是这些 代码不多
    funcs.reduce((a, b) => (...args) => a(b(...args)))
    可以简写成这样子 没错吧 (es6语法不熟练的同学自行去补补课,reduce就是一个叠加器叠加执行)
    举个栗子🌰
    var fn1 = val => 'fn1-' + val
    var fn2 = val => 'fn2-' + val 
    var fn3 = val => 'fn3-' + val 
    compose(fn1,fn2,fn3)('测试')
    fn1-fn2-fn3-测试  这是执行结果
    第一次执行开始
    a = fn1
    b = fn2 
    (...args) => a(b(...args))   
    (...args) = > fn1(fn2(...args))
    第一步执行完 a = (...ag) => fn1(fn2(...ag))    //如果不理解怎么变成这个样子的去看看 reduce 函数
    第二步开始执行
    a = (...args) => fn1(fn2(...args))
    b = fn3 
    (...args) => a(b(...args))   
    (...args) => a( fn3(...args) ) 
    (...args) => fn1(fn2(fn3(...args))) 
    咦咋就变成这样了  a = (...args) => fn1(fn2(...args))  执行这一步的时候 b(...args)作为了参数传进了a 也就是 ...args =  b(...args) = fn3(...args)
    最后的运行结果就是 (...args) => fn1(fn2(fn3(...args))) 
    

    总结一下compose 就是函数叠加执行内层的执行结果时外层的参数 生成过程是由内向外,执行过程是由外向内

    这是咱们从源码解读、代码演示、官方注释、上都能看出来的结果。记住最后面最右面的最先执行 以后 compose 不再说了
    compose 说完了
    回到咱们最初的代码

    import { createStore, applyMiddleware, compose } from 'redux';
    
    import rootReducer from' ../reducers';
    const store = compose(
      applyMiddleware(f1, f2, f3),
    )(createStore)(rootReducer, initialState);
    

    再看 applyMiddleware 里面到底执行了什么东东

    export default function applyMiddleware(...middlewares) {
      return (createStore) => (reducer, preloadedState, enhancer) => {
        const store = createStore(reducer, preloadedState, enhancer)
        let dispatch = store.dispatch
        let chain = []
    
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    }
    

    也是我从node_modules里翻出来的
    代码看上去要比之前解读过得compose长一点 别怕 一点点看
    首先可以看到 运行 applyMiddleware 后 会返回一个函数 (高阶函数的用法)需要传递createStore作为参数 然后又返回一个函数 需要传递reducer, preloadedState, enhancer三个值作为参数
    感觉比较乱我们用图参照的比较一下就清晰了


    参数对照图

    很清晰把每一个都对上了
    接下来继续看
    const store = createStore(reducer, preloadedState, enhancer)
    创建了 store 这个对象是 createStore生成的
    那再来看下 createStore 内部做了些什么 (可能你有点不习惯,我在探索技术的时候就是一层层往下屡着看,看到不会的就往下面找,直至全部搞懂)

    createStore 内部

    createStore内部源码就比较多了 咱们不每一行解读了(本来本文说的就挺多createStore如果再每一行过一遍文章太长了)

    function createStore(reducer, preloadedState, enhancer) {
      var _ref2;
    
      if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        enhancer = preloadedState;
        preloadedState = undefined;
      }
    
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.');
        }
    
        return enhancer(createStore)(reducer, preloadedState);
      }
      ...
    }
    

    执行 createStore需要传递三个参数reducer, preloadedState, enhancer 执行后会返回 一个对象
    createStore会判断 第二个参数preloadedState是否是个函数并且enhancer 是否是undefined 如果成立
    enhancer = preloadedState;
    preloadedState = undefined;
    然后如果enhancer 是函数 就去执行 传递的参数还是 createStore
    这个不多说了 咱们没有传递 enhancer
    preloadedState 正常情况就是默认值 也可以不传
    执行了之后会返回一个对象

    return {
        dispatch,  // 传入 action,调用 reducer 及触发 subscribe 绑定的监听函数
        subscribe, //subscribe 这个函数是用来去订阅 store 的变化
        getState,  //获取最新的State
        replaceReducer,  // 用新的 reducer 代替当前的 reducer,使用不多
        [$$observable]: observable
      }
    

    (其实除了 dispatch 其他的你基本上用不到 dispatch 也是就几处用到,因为 bindActionCreators帮我们隐形调用了dispatch(action)这里有机会咱们以后详细说)
    搞定了 createStore的返回值下面的代码就能看懂了

    回头继续看applyMiddleware

    export default function applyMiddleware(...middlewares) {
      return (createStore) => (reducer, preloadedState, enhancer) => {
        const store = createStore(reducer, preloadedState, enhancer)
        let dispatch = store.dispatch
        let chain = []
    
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    }
    

    创建了 chain 数组
    创建 middlewareAPI 里面 有getState 和 dispatch
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    先把所有的中间件 带着 middlewareAPI 运行一遍 保存到chain 中
    dispatch = compose(...chain)(store.dispatch)
    又看见了compose
    很好 之前说了 compose 是函数叠加执行
    执行刚刚 带着middlewareAPI 执行了一遍返回的函数集合chain
    再传入store.dispatch作为参数 (是最后一个函数的参数呦)
    代入代码咱们看一眼

    const store = compose(
      applyMiddleware(f1, f2, f3),
    )(createStore)(rootReducer, initialState);
    //这是之前的例子
    //chain是个数组 里面的内容就是每一个middleware带着middlewareAPI执行了一遍
    //现在先不要管f1 f2 f3内部是如何执行的
    //chain内的执行结果作为参数传入compose 在让他们叠加执行(就是把每个middleware串联起来)并赋值给dispatch
    //dispatch = f1(f2(f3(store.dispatch))))  这是 compose执行后的样子
    //现在可以看到 执行 dispatch 就是执行 f1(f2(f3(store.dispatch))))  也就是每个 middleware依次执行
    
    //中间件的编写格式
    const f1 = store => next => action => {
        next(action)
    }
    const f2 = store => next => action => {
        action = action.type + '_SUCCESS'
        next(action)
    }
    const f3 = store => next => action => {
        next(action)
    }
    

    三个箭头 第一次看感觉头都大
    别怕在画一个图 就清晰了


    参数对照图

    这回清楚了吗
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    middleware 的第一个参数store 是middlewareAPI 里面有getState 和 dispatch
    dispatch = compose(...chain)(store.dispatch)
    middleware 的第二个参数next是store.dispatch
    看到这可能你就有疑问了 多么语义化的参数 next 以我小学三年级的英语水平 一眼看出这是下一个的意思 可是下一个为什么 是 store.dispatch 很不合逻辑啊
    在看下 compose的函数串联
    f1(f2(f3(store.dispatch))))
    仔细看
    f1的next是 f2
    f2的next 是 f3
    f3的next 是store.dispatch
    之前我也强调过 store.dispatch 只是最后一个函数的参数 没有理解的同学在仔细品味品味
    最后一个action参数 应该都了解 这是dispatch执行时咱们自己加的 里面有个type值对应reducer 触发更改state用的

    再回到现实项目看看 (就是这样,很真实,很落地)

    applyMiddleware(thunk, api, reduxRouterMiddleware)
    thunk 是 redux-thunk 提供的

    function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
    
        return next(action);
      };
    }
    
    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;
    
    export default thunk;
    

    内部的源码是这样
    其实thunk 内部就是 判断你发起的action是不是函数 是函数就把dispatch, getState 作为参数传给你 不是函数就只交给下一个 next(action)
    有的项目是直接把异步请求放到了actionCreator 里 这样 action是个函数 请求回来又可以触发dispacth
    我所做的项目不是用的这种形式,在actionCreator中写了一个函数用于动态的创建action

    import * as types from '../../constants/actions/common';
    
    export function request(apiName, params, opts = {}) {
        return (dispatch, getState) => {
            let action = {
                'API': {
                    apiName: apiName,
                    params: params,
                    opts: opts
                },
                type: types.API_REQUEST
            };
            return dispatch(action);
        };
    }
    
    //其他地方调用复用的方法如下:
    export { request } from './request';
    

    是在thunk后面又自己加了个中间件 api 然后再 这里可以做更多的控制 请求中的action 成功的 失败的 请求拦截的

    export default store => next => action => {
        let API_OPT = action['API'];
    
        if (!API_OPT) {
            //我们约定这个没声明,就不是我们设计的异步action,执行下一个中间件
            return next(action);
        }
        在这里可以开启你的真正的逻辑了
        ...
    };
    

    基本上到这里就是全部内容了 讲解了 createStore, applyMiddleware, compose,thunk
    我当初在看这里的时候也不是一下全能吸收的,尤其是next(action)这里
    如果你的next 这里同样有和我一样的疑惑

    function col(val){
        console.log(val)
        console.log('执行')
    }
    function f1(next){
        console.log('f1')
        return function(action){
            console.log(action,'f1Action')
            next(action/2)
        }
    }
    
    function f2(next){
        console.log('f2')
        return function(action){
            console.log(action,'f2Action')
            next(action+2)
        }
    }
    
    function f3(next){
        console.log('f3')
        return function(action){
            console.log(action,'f3Action')
            next(action+1)
        }
    }
    
    var dispatch = f1(f2(f3(col)))
    dispatch(1)
    

    运行下试试看看
    在写这篇文章的时候 也找了很多材料
    https://segmentfault.com/a/1190000007843340
    这是我看见其中比较好的,好文要推荐,一同学习一同进步

    相关文章

      网友评论

          本文标题:redux applyMiddleware 中间件原理

          本文链接:https://www.haomeiwen.com/subject/bctidqtx.html