体验 JavaScript 函数式编程的优美——redux下compose函数

背景 本以为对javascript掌握还算可以,但是当看了Redux库的applyMiddleware(中间件)的代码后,我恍然地认识到了javascript是可以这么灵活,js 下的函数式编程是这么的美妙呀~。


每每看到这段二十来行的代码,都会细品。。。(还是因为自己菜看不懂)。

applyMiddleware 的作用

中间件之所以是中间件,表面上的dispatch(action),实际上中间件已经将dispatchaction重重处理,最终再经手最“原始”的dispatch,并交给Reducer,总而言之,中间件增强了dispatch的功能,让其不仅可以处理异步(redux-thunk/redux-saga),还可以实现redux-logger这样的功能。一张图解释中间的地位:
在这里插入图片描述
分析源码之前对相关API过下:

  • 原本没有使用中间件时候写法createStore的写法:
const store createStore(reducer, initialState);
  • 使用中间件createStore的写法
const store = createStore(reducer, applyMiddleware(thunk, logger));
  • 对于applyMiddlewareapi其实也可以构造store的,虽然不常这样用,这里先给出,后边源码分析会给出解释(其实就是做了一个参数判断)。
const store = applyMiddleware(...middlewares)(createStore)(reducer, initialState)

上述api使用中一些参数的解释:

  • 其中middlewares是中间件组成的数组。而且applyMiddleware接受的参数都是中间件,例如上边的redux-thunk处理异步actionlogger打印dispatch前后的状态数据。
  • createStoreapplyMiddleware都是import自 Redux 的方法。文主要对这两个方法进行分析。
  • reducer纯函数作用是接受action和旧的state返回新的state
  • initialState初始的state。

createStore源码

  • preloadedState 对应上文提到的 initalState
  • enhancer 对应上文提到的 applyMiddleware(thunk, logger) 的运行结果(这个运行结果实际上是一个函数——compose后边会详述)。
export default function createStore(reducer, preloadedState, enhancer) {
  /* 
  对于 preloadedState 的判断
  如果 preloadedState 和 enhancer 都是函数,
  或者 enhancer 之后有多个函数,报错
  */
   if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }
/*
	如果 preloadedState 函数,而 enhancer没有传,
	说明 preloadedState 其实是 enhancer 
*/
  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.')
    }
	// 返回使用过中间件后构造出的 store
    return enhancer(createStore)(reducer, preloadedState)
  }
	
  //省略....初始化 store ,并对 store 做一些发布订阅
}

看到这里就明白了上边为什么说

对于applyMiddlewareapi其实也可以构造store的。

其实createStore就是做了一层判断,如果你们想要使用更高级的中间件,我可以帮你搞定,并把自己creastore给传进applyMiddleware,这也说明最终的store毕竟还是要靠这个createStore来做(毕竟store那么多的订阅逻辑,肯定不能重复再写一遍)。


applyMiddleware 解析

千呼万唤始出来,犹抱琵琶半遮面。

接下里到重头戏了。

首先明确,这个结果肯定是一个store,这是没跑的(有中间件情况下的createStore函数返回值嘛),

applyMiddleware(createStore)(reducer, preloadedState)

而且store 肯定包含 getStatedispatch 的方法。
那么结合文章开头的那个图片,可以猜到,applyMiddleware一定是这样的结构

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    /*
    	1.构建 store 
    	2.增强 dispatch 的功能。
    */
    return {
      ...store,
      dispatch
    }
  }
}
  • 我们心里大可去接受:中间件的作用就是增强dispatch的功能,那么接下来的基本思路肯定就是创建store,然后覆写这个dispatch
    在此之前先瞄一下中间件的结构:
export default ({getState, dispatch}) => next => action => {
    /*在某个合适的时机或者地方*/
    next(action);
}

可以发现:其实中间件就是一个回调函数,每次调用完毕会把自己的处理结果给下一个中间件(回调)。

  • 利用上边的中间件(函数)+原始dispatch(createStore返回的)得到一个增强版的dispatch
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)
 };
 // 1、将store对象的基本方法传递给每个中间件并依次调用中间件
 const chain = 
 middlewares.map(middleware => middleware(middlewareAPI));
 // 2、改变dispatch指向,并将最初的dispatch传递给compose
 dispatch = compose(...chain)(store.dispatch);

注释1:先定义了一个运行一定会报错的dispatch,然后注入到每个中间件中。这样做是为了防止在中间件构建过程中调用dispatch,不然,可以想象在一个中间件(函数)中直接调用原始dispatch,那么其他的中间件会被跳过(类似于流水线上,中间一个机器不听话,直接将物品丢到了最后一个机器,那么这样做出来的物品肯定是不完美的)。
在这里插入图片描述
注意:此处每个中间件(函数)已经运行了一层,得到:

a = next => action => {
    /*在某个地方*/
    next(action);
}

注释2:改变dispatch的指向。其中用到了compose函数,compose函数的功能就是集所有中间件功能于大成,将整个流水线拼装起来。(不得不吹, compose函数写得真的赞,深深体味到了函数式编程的优美)。

function compose(...chain){
	//边界条件
    if (chain.length === 0) {
        return arg => arg;
    }
    if(chain.length === 1){
        return chain[0];
    }
    // 核心
	return chain.reduce( (a,b) => (...args) => a(b(...args)) );
}

reduce 起到迭代的作用,详见

  • 最终,我们得到了一整条流水线compose(…chain),然后通过原始dispatch去“启动”这条流水线。
    具体分析一个例子:
const chain = [a, b, c]; 
compose(...chain)(store.dispatch);
//最终得到的结果:a(b(c(dispatch)))

上图做解释:
在这里插入图片描述

  • 最最终用这个a(b(c(dispatch)))增强版的dispatch代替store下原本的dispatch
 return {
     ...store,
     dispatch
   }

注意:此处的中间件(函数)已经又调用了一层,得到:

dispatch = action => {
    /*在某个地方*/
    next(action);
}

为了帮助理解,那如果我们在某个地方调用了这个增强版的dispatch那么过程是什么样子呢?
上图:在这里插入图片描述

以身试法

  • 对一篇文章进行单词提取。以, . ; : ' " ?为界限进行分割。
/* 
    对一段引文文章,获取每个单词,
    对 , . ; : ' " ? 进行分割 
*/

function splitArticle(base) {
    return function (content) {
        let arr = content.split(base);
        return arr.reduce((total, item) => total + item, '');
    }
}

const g = function (content) {
    const base = [',', '.', ';', ':', '\'', '"', '?'];
    const chain = base.map( item => splitArticle(item));
    let compose = chain.reduce((a, b) => (str) => {
        return a(b(str));
    });
    return compose(content).split(" ");
}

let content = 'Among humans, smiling is an expression denoting pleasure, sociability, happiness, joy or amusement. It is distinct from a similar but usually involuntary expression of anxiety known as a grimace. Although cross-cultural studies have shown that smiling is a means of communication throughout the world,[1] there are large differences among different cultures, religions and societies, with some using smiles to convey confusion or embarrassment.';

let result = g(content);
console.log('result', result);

总结

对于compose 函数这样的写法,可以用在 对一个对象多次类似连续操作 的应用场景下。

  • 连续。上一步处理的结果是我感兴趣的,而且我处理的结果同样会被下个处理感兴趣。这样类似环环相扣的抽象感觉。
  • 类似。只有类似的处理逻辑,才能借助reduce组合成一个流水线

但感觉连续的要求可能要更多一点,因为毕竟再不相同的逻辑场景,我们可以去做一些改动让他们结构相似就可以了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值