zoukankan      html  css  js  c++  java
  • redux源码阅读之compose,applyMiddleware

    我的观点是,看别人的源码,不追求一定要能原样造轮子,单纯就是学习知识,对于程序员的提高就足够了。在阅读redux的compose源码之前,我们先学一些前置的知识。

    redux源码阅读之compose,applyMiddleware

    看别人的源码就是学习知识。我们先学一些东西。

    rest参数

    形式为...变量名,用于获取函数的多余参数 ,该变量将多余的参数放入数组中, 只能是参数的最后一个。

    function rest(...args){
      console.log(args);
    };
    rest('arg1','arg2')
    

    那么args变量就是一个数组['arg1','arg2']。

    扩展运算符

    扩展运算符也是三个点。好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列

    arr.reduce(callback[initialValue])

    个人感觉reduce用法非常魔幻,要搞明白它之后,才能明白compose。
    MDN的reduce文档:
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

    使用

    const array1 = [1, 2, 3, 4];
    const reducer = (accumulator, currentValue) => accumulator + currentValue;
    
    // 1 + 2 + 3 + 4
    console.log(array1.reduce(reducer));
    // 10
    
    // 5 + 1 + 2 + 3 + 4
    console.log(array1.reduce(reducer, 5));
    // 15
    
    array1.reduce((accumulator, currentValue) => accumulator + currentValue,5)
    

    根据MDN的定义,
    数组的reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

    arr.reduce(callback[initialValue])

    实际就是arr.reduce(callback, initialValue);

    callback 函数接收4个参数:

    Accumulator (acc) (累加器)
    Current Value (cur) (当前值)
    Current Index (idx) (当前索引)
    Source Array (src) (源数组)
    

    initialValue 可选

    作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 不传这个初始值initialVale,且在空数组上调用 reduce 将报 Uncaught TypeError: Reduce of empty array with no initial value的错误。

    pip

    研究compose之前,我们再来看MDN上reduce的应用,一个功能型函数管道。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#功能型函数管道

    类似的我们来写一个。

    const pipe = (...fns) => 
      (args) => fns.reduce(
        (args, fn) => fn(args)
      ,args)
    

    吐槽一下,就是这种es6写法,完全不写return,很多es5过来的人不习惯,所以很多源码很难看清楚。我们改写一下,传入参数字符串‘test’,然后执行这个函数。就能明白pip函数的作用就是顺序执行了addFont,toUpper。

    args就是'test',...fns就是rest参数,pip函数里的fns参数就是数组[addfont,toUpper]。

    
    const pipe = (...fns) => {
      return (args) => {
        return fns.reduce((args, fn) => {
          return fn(args)
        }, args)
      }
    }
    
    const toUpper = (value) => {
      return value.toUpperCase();
    }
    
    const addFont = (value) => {
      return 'hello plus!' + value;
    }
    
    console.log(pipe(addFont,toUpper)('test'));
    // HELLO PLUS!TEST
    
    

    pipe(addFont,toUpper)之后,返回一个函数.
    pipe(addFont,toUpper)('test')形如

    (args) => {
        return fns.reduce((args, fn) => {
          return fn(args)
        }, args)
      }('test');
    

    再执行就是

    fns.reduce(('test',fn)=>{
        return fn('test');
    },'test')
    

    这样子看代码,这里的reduce应该似乎比较好懂的。

    compose

    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))).
     */
    
    export default function compose(...funcs) {
      if (funcs.length === 0) {
        return arg => arg
      }
    
      if (funcs.length === 1) {
        return funcs[0]
      }
    
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    

    跟pip函数对比

    console.log(pipe(addFont,toUpper)('test'));
    // HELLO PLUS!TEST
    
    console.log(compose(addFont,toUpper)('test'));
    // hello plus!TEST
    

    compose正好相反,后添加的方法先执行。然后写的也很玄学让人费解。让我们改成pip函数一样,也是同样的效果,感觉这样子就跟上面的pip一样好理解一些了呢。

    export default function compose(...funcs) {
      if (funcs.length === 0) {
        return arg => arg
      }
    
      if (funcs.length === 1) {
        return funcs[0]
      }
    
      return (...funcs) => (...args) => funcs.reduce((a, b)  => a(b(...args)))
    }
    console.log(compose(addFont,toUpper)('test'));
    // hello plus!TEST
    

    applyMiddleware

    我们再来看redux引入中间件的用法,

    const store = createStore(
      reducer,
      applyMiddleware(...middleware)
    );
    

    以及applyMiddleware方法源码

    export default function applyMiddleware(...middlewares) {
      return createStore => (...args) => {
        const store = createStore(...args)
        let dispatch = () => {
          throw new Error(
            `Dispatching while constructing your middleware is not allowed. ` +
              `Other middleware would not be applied to this dispatch.`
          )
        }
    
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    }
    

    实际上所谓redux的中间件就是改写了store.dipatch方法。比如我们自己实现一个实现一个打印action的type的redux中间件,我们就可以这样写。

    ./redux-middlewares/log.js

    const logger = ()=>{
        return ({dispatch,getState}) => next => action =>{
            console.log('dispatching', action);
            return next(action);
        }
    };
    
    export default logger;
    

    跟别的redux中间件一起引入

    import createSaga from 'redux-saga';
    import createLog from './redux-middlewares/log';
    const saga = createSaga(); 
    const log = createLog();
    const middlewares = [saga,log]
    const store = createStore(
      rootReducer,
      applyMiddleware(...middlewares)
    );
    store.dispatch({type:'myAction'});
    
    // 打印 dispatching {type: "myAction"}
    

    这里
    redux的添加中间件
    applyMiddleware(...middleware) 就等于这里的
    dispatch = compose(...chain)(store.dispatch).
    跟我们实行
    compose(addFont,toUpper)('test')是一样的效果。

    参考文章:

    https://www.cnblogs.com/cloud-/p/7282188.html

  • 相关阅读:
    Effective Java 第三版——72. 赞成使用标准异常
    Effective Java 第三版——71. 避免不必要地使用检查异常
    Effective Java 第三版——70. 对可恢复条件使用检查异常,对编程错误使用运行时异常
    Effective Java 第三版——69. 仅在发生异常的条件下使用异常
    Effective Java 第三版——68. 遵守普遍接受的命名约定
    Effective Java 第三版——67. 明智谨慎地进行优化
    Effective Java 第三版——66. 明智谨慎地使用本地方法
    Effective Java 第三版——65. 接口优于反射
    Effective Java 第三版——64. 通过对象的接口引用对象
    Effective Java 第三版——63. 注意字符串连接的性能
  • 原文地址:https://www.cnblogs.com/liaozhenting/p/10166671.html
Copyright © 2011-2022 走看看