Redux中间件对闭包的一个巧妙使用

原创 2017年09月11日 22:06:49

最近在看Redux的源码,发现Redux在使用中间件applyMiddleware.js的源码中,有一个对闭包非常巧妙的使用,解决了“鸡生蛋,蛋生鸡”的问题,特分享给大家。

Redux中间件的函数签名形式如下:

({dispatch, getState}) => next => action => {
   // 函数体
}

applyMiddleware.js中的函数applyMiddleware(…middlewares)用于根据中间件生成action经过的中间件链。先来看一个错误版本的实现:

/*
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: store.dispatch
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    var dispatch = compose(...chain)(store.dispatch)    //compose(f, g, h) 等价于函数
                                                    //(...args)=>f(g(h(args)))

    return {
      ...store,
      dispatch
    }
  }

核心逻辑是chain = middlewares.map(middleware => middleware(middlewareAPI))和dispatch = compose(…chain)(store.dispatch)这两行。第1句代码是根据中间件生成一个数组chain,chain的元素是签名为next => action => {…}形式的函数,每个元素就是最终中间件链上的一环。第2句代码利用compose函数,将chain中的函数元素组成一个“洋葱式”的大函数,chain的每个函数元素相当于一层洋葱表皮。Redux发送的每一个action都会由外到内依次经过每一层函数的处理。假设有3层函数,从外到内依次是a,b,c,函数的实际调用过程是,a接收到action,在a函数体内会调用b(a的参数next,指向的就是b),并把action传递给b,然后b调用c(b的参数next指向的就是c),同时也把action传递给c,c的参数next指向的是原始的store.dispatch,因此是action dispatch的最后一环。这样分析下来,程序是没有问题的,但当我们的中间件需要直接使用dispatch函数时,问题就出来了。例如,常用于发送异步action的中间件redux-thunk,就需要在异步action中使用dispatch:

export function fetchPosts(subreddit) {  
  return function (dispatch) {
    dispatch(requestPosts(subreddit))
    return fetch(`https://www.reddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
        error => console.log('An error occured.', error)
      )
      .then(json =>
        dispatch(receivePosts(subreddit, json))
      )
  }
}

fetchPosts使用的dispatch,是redux-thunk传递过来的,指向的是middlewareAPI对象中的dispatch,实际等于store.dispatch。当执行dispatch(requestPosts(subreddit))时,这个action直接就到了最后一环节的处理,跳过了redux-thunk中间件之后的其他中间件的处理,显然是不合适的。我们希望的方式是,这个action依然会从最外层的中间件开始,由外到内经过每一层中间件的处理。所以,这里使用的dispatch函数不能等于store.dispatch,应该等于compose(…chain)(store.dispatch),只有这样,发送的action才能经过每一层中间件的处理。现在问题出来了,chain = middlewares.map(middleware => middleware(middlewareAPI))需要使用dispatch = compose(…chain)(store.dispatch)返回的dispatch函数,而dispatch = compose(…chain)(store.dispatch)的执行又依赖于chain = middlewares.map(middleware => middleware(middlewareAPI))的执行结果,我们进入死循环了。

问题的解决方案就是闭包。当我们定义middlewareAPI的dispatch时,不直接把它指向store.dispatch,而是定义一个新的函数,在函数中引用外部的一个局部变量dispatch,这样就形成了一个闭包,外部dispatch变量的变化会同步反映到内部函数中。如下所示:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var dispatch = store.dispatch;   // 需要有初始值,保证中间件在初始化过程中也可以正常使用dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)    // 通过闭包引用外部的dispatch变量
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)    //compose(f, g, h) 等价于函数
                                                    //(...args)=>f(g(h(args)))

    return {
      ...store,
      dispatch
    }
  }

这样,“鸡生蛋,蛋生鸡”的问题就解决了。如果这个例子对你来说太复杂,可以用下面这个简化的例子帮助你理解:

const middleware = ({dispatch}) => (next) => (number) => {
  console.log("in middleware");
  if(number !== 0){
    return dispatch(--number);
  }

  return next(number);
}

function test() {
  var dispatch = (number) => { 
    console.log("original dispatch");
    return number;
  };
  var middlewareAPI = {
    dispatch
  }

  dispatch = middleware(middlewareAPI)(dispatch);

  return {
    dispatch
  }
}

var {dispatch} = test();
dispatch(3);

//输出:
"in middleware"
"original dispatch"

const middleware = ({dispatch}) => (next) => (number) => {
  console.log("in middleware");
  if(number !== 0){
    return dispatch(--number);
  }

  return next(number);
}

function test() {
  var dispatch = (number) => { 
    console.log("original dispatch");
    return number;
  };
  var middlewareAPI = {
    dispatch: (number) => {dispatch(number);}
  }

  dispatch = middleware(middlewareAPI)(dispatch);

  return {
    dispatch
  }
}

var {dispatch} = test();
dispatch(3);

//输出 
"in middleware"
"in middleware"
"in middleware"
"in middleware"
"original dispatch"

第二种方式,middleware中dispatch的number会再次经历中间件的处理,当number=3,2,1,0时,都会进入一次middleware函数,当number=0时,next(0)调用的是test中定义的初始dispatch函数,因此不再经过middleware的处理。

版权声明:本文为博主原创文章,未经博主允许不得转载。

Redux 入门教程(二):中间件与异步操作

作者: 阮一峰 日期: 2016年9月20日 上一篇文章,我介绍了 Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染...
  • sinat_17775997
  • sinat_17775997
  • 2016年10月11日 21:57
  • 561

理解redux和redux的中间件redux-thunk的认识

一、Action的认识 简单点说Action就是一个对象,一个必须带key为type的对象[value是自己定义的],其他的key就根据用户自己喜好自己定义: 以下都是action的定义 1、{t...
  • kuangshp128
  • kuangshp128
  • 2017年03月28日 10:29
  • 7930

redux-applyMiddleware实现理解+自定义中间件

前言: http://www.cnblogs.com/miaowwwww/p/6265323.html   终于好好理解了middleware。。。。 1.redux middl...
  • sinat_17775997
  • sinat_17775997
  • 2017年02月19日 15:23
  • 1034

关于Redux的一些总结(一):Action & 中间件 & 异步

在浅说Flux开发中,简单介绍了Flux及其开发方式。Flux可以说是一个框架,其有本身的 Dispatcher 接口供开发者;也可以说是一种数据流单向控制的架构设计,围绕单向数据流的核心,其定义了一...
  • linyouhappy
  • linyouhappy
  • 2016年09月03日 14:48
  • 3260

从函数的柯里化,看Redux中间件的实现

简介:同步请求时,dispatch(action)发出请求,到接受请求reducer(state,action)是同步的。如果当我们需要异步请求时,状态应该变为dispatch(action)——wa...
  • liwusen
  • liwusen
  • 2016年12月20日 16:26
  • 666

redux源码解析

用npm安装redux,看目录结构 npm install redux 找到src目录 * index.js: redux主文件,主要对外暴露核心api * createStore.j...
  • sysuzhyupeng
  • sysuzhyupeng
  • 2017年02月06日 14:25
  • 609

打造Redux中间件

简单的基本中间件:const customMiddleware = store => next => action => { if(action.type !== 'CUSTOM_ACT_TY...
  • future_challenger
  • future_challenger
  • 2017年03月01日 22:31
  • 420

[译]深入浅出Redux中间件

[译]深入浅出Redux中间件 时间 2015-10-09 09:37:12  Kazaff 原文  http://blog.kazaff.me/2015/10/09/[译]Redux中间件深...
  • xiaolei1982
  • xiaolei1982
  • 2016年05月03日 09:54
  • 284

redux的中间件

action发出以后 reducer立即算出state,这个叫做同步,,,action发出以后过一段时间在执行reducer,这叫异步,那么异步怎么办 怎么才能reducer在异步操作结束后...
  • zhanglongdream
  • zhanglongdream
  • 2017年01月24日 21:51
  • 187

关于Redux的一些总结(一):Action & 中间件 & 异步

在浅说Flux开发中,简单介绍了Flux及其开发方式。Flux可以说是一个框架,其有本身的 Dispatcher 接口供开发者;也可以说是一种数据流单向控制的架构设计,围绕单向数据流的核心,其定义了一...
  • u011043843
  • u011043843
  • 2016年08月26日 10:42
  • 5623
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Redux中间件对闭包的一个巧妙使用
举报原因:
原因补充:

(最多只允许输入30个字)