博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redux中间件对闭包的一个巧妙使用
阅读量:6831 次
发布时间:2019-06-26

本文共 4578 字,大约阅读时间需要 15 分钟。

最近在看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的处理。


欢迎关注我的公众号:老干部的大前端,领取21本大前端精选书籍!

转载地址:http://annkl.baihongyu.com/

你可能感兴趣的文章
string to float
查看>>
iOS开发3:UITextField控件的属性
查看>>
netty源码解析
查看>>
ODOO Unable To Find Wkhtmltopdf On This System.
查看>>
java关键字--this
查看>>
codewars065 - Backwards Read Primes
查看>>
为什么调用 FragmentPagerAdapter.notifyDataSetChanged...
查看>>
class文件加密,class文件数据库加载
查看>>
Kubernetes 集群安装
查看>>
apache server
查看>>
forward与sendRedirect
查看>>
spring管理serlvet
查看>>
SDL_AudioSpec结构体分析
查看>>
Maven工程发布tomcat无lib包问题
查看>>
使用Scrapy来爬取自己的CSDN文章 (2)
查看>>
同时查看集群中多台机器日志的简单的shell脚本
查看>>
GemFire核心功能介绍
查看>>
ZUUL源码分析 <一
查看>>
Spring 启动记录(11)
查看>>
SphereView for tag cloud
查看>>