Redux 的 action 是一个 JS 对象,它表明了如何对 store 进行修改。但是 Redux 的中间件机制使action creator 不光可以返回 action 对象,也可以返回 action 函数, middleware 会拦截自己感兴趣的 action 类型,然后进行某些共性的操作。比如在拉取服务器数据时,如果没有中间件机制,我们可能需要首先请求数据,数据到达后,将数据给 action creator得到一个 action 对象,再 dispatch action;有了redux-promise 中间件后,就可以将数据请求的逻辑直接放在 action creator 中,然后中间件自动拦截Promise类型的 action,等待 action fulfilled 之后,再 dispatch 最终的 action 对象

一、middleware

middleware 相当于是对 store.dispatch()函数的包装,一些针对特定action的处理,或者对所有action都进行的处理放进middleware中进行。

middleware使 action creator 不光可以返回 action 对象,也可以返回 action 函数,因为 middleware 会拦截自己感兴趣的 action ,然后对那个 action 进行某些共性的操作,比如调用 Ajax调用。

middleware可以有多个,每个 middleware 类似以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const oneMiddleware = ({ dispatch, getState} ) => (next) => (action) => {
// 如果不是自己感兴趣的action,则调用之后的中间件
if(typeof action !== "这个中间件感兴趣的action") {
return next(action)
}
// 对这个action进行特殊处理,比如拦截异步操作 (callAPI 是action传进来的)
dispatch(Object.assign({}, payload, {
type: requestType
})) return callAPI().then(
response => dispatch(Object.assign({}, payload, {
response,
type: successType
})),
error => dispatch(Object.assign({}, payload, {
error,
type: failureType
}))
)
}

二、 applyMiddleware 源码解析

下面是redux/lib/index.js中applyMiddleware的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function applyMiddleware() {
// 初始化一个 middlewares 数组,里面放所有参数传进来的 middleware
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
// createStore 在传递第三个参数时,会按下面的语句返回
// return enhancer(createStore)(reducer, preloadedState);
// enhancer在这里就是 applyMiddleware(...middleware) 的返回值
// 之所以是对 createStore 应用 middleware,而不是 store,是为了防止对store重复使用一个middleware
return function (createStore) {
return function () { // 将 createStore 时传入的 reducer 和 preloadedState 放入 args 数组中
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
} // 创建一个 store
var store = createStore.apply(undefined, args);
// 在创建 middleware 时 调用dispatch是不允许的
var _dispatch = function dispatch() {
throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
}; var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
}; // 将getState和 dispatch作为参数传递给每个 middleware
// chain 函数数组中每个函数都是 (next) => (action) => {...} 形式
// 这里的 dispatch 还是 _dispatch
// 也就是说 middleware,如果像以下形式一样调用了 dispatch 会报错
// const oneMiddleware = ({dispatch, getState}) => {
// diapatch(someAction(...)) // 在这里调用dispatch,调用的其实是 _dispatch,会报错
// return (next) => (action) => {
// ....
// }
// }
// 之所以不让在创建时刻调用dispatch,是因为这时的 dispatch 就是 createStore产生的 dispatch,这是如果调用 dispatch 会导致没有任何 middleware 被应用,那就没有意义了
var chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
}); _dispatch = compose.apply(undefined, chain)(store.dispatch); // 替换 store 的 dispatch
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}

总结三个要点:

  • 当使用了applyMiddleware时,实际是applyMiddleware(...middleware)(createStore)(reducers, preloadedState)这样的调用方式。将 createStore 作为参数传入,而不是直接对 store 应用middleware,主要是为了防止对 store 引用多次相同的middleware
  • 不能在创建时刻调用dispatch,因为这时如果非要调用 dispatch,那么只能调用 createStore 的 dispatch,不会经过 middleware。所以源码在这步干脆就把 dispatch 替换成了一个会抛出错误的函数。
  • 使用 compose 函数将多个 middleware 变为一层包一层的洋葱式的执行方式,洋葱的核心是 createStore 的 dispatch

下面是compose函数的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
大专栏  理解 Redux 中间件机制">21
22
23
// compose.apply(undefined, chain)(store.dispatch);
function compose() {
// 将chain 函数数组都copy到新的 funcs数组
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
// 如果没有 middleware,则返回的dispatch函数就是 createStore 的 dispatch,相当于没做任何处理
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
// 如果只有一个 middleware,则直接返回那个 middleware
if (funcs.length === 1) {
return funcs[0];
}
// 对数组中的每个元素(从左到右)应用某个函数
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}

这里有两个要点:

  • compose 函数返回的是一个以 store.dispatch 函数为参数的函数,这步就是为洋葱式的执行结构填上核心
  • compose 函数的核心是 Array.prototype.reduce 的应用,它的作用是对数组中的每个元素(从左到右)应用某个函数,最后返回一个结果

reduce 函数具体可以看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const chain1 = (next) => (action) => {
console.log('before action1');
const returnedvalue = next(action);
console.log('after action1');
return returnedvalue;
} const chain2 = (next) => (action) => {
console.log('before action2');
const returnedvalue = next(action);
console.log('after action2');
return returnedvalue;
} const func = [chain1, chain2]; const newFunc = func.reduce((a, b) => {
return function () {
console.log(arguments);
return a(b.apply(undefined, arguments))
}
}) const dispatch = (action) => {
console.log('this is dispatch');
return 'final result'
} const lastFunc = newFunc(dispatch) console.log(lastFunc({ type: 'some action' })); // { '0': [Function: dispatch] }
// before action1
// before action2
// this is dispatch
// after action2
// after action1
// final result

总之最后 applyMiddleware函数最后返回的是一个 dispatch 被 middleware 包裹后的 store。这样当dispatch 一个 action时,action会先通过每个 middleware,middleware 可以根据 action的内容或者类型(比如是否为function)等信息来拦截自己感兴趣的 action,然后再对这个 action 特殊处理。

三、 redux-promise 源码解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function promiseMiddleware(_ref) {
var dispatch = _ref.dispatch;
// dispatch(); // 这个语句会报错
return function (next) {
return function (action) {
if (!_fluxStandardAction.isFSA(action)) {
return isPromise(action) ? action.then(dispatch) : next(action);
} return isPromise(action.payload) ? action.payload.then(function (result) {
console.log(dispatch.toString());
return dispatch(_extends({}, action, { payload: result }));
}, function (error) {
return dispatch(_extends({}, action, { payload: error, error: true }));
}) : next(action);
};
};
}

以上就是 redux-promise 的源码,它会拦截是 Promise 对象 或者 payload 属性是promise 对象的action。看上去还是非常简单的。

但是这里有一个要点,就是在 applyMiddleware 中,有一个用 middleware 函数数组 构建 chain 函数数组的过程,为了防止 middleware 在构建过程中调用 dispatch ,它传递的 dispatch 函数,函数体是这样的

1
2
3
4
5
6
7
8
9
10
var _dispatch = function dispatch() {
throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
}; var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
};

==那么为什么在构建时,middleware 调用 dispatch 会出错,在构建之后 middleware 调用 dispatch 不会出错呢?==

我们知道函数是引用传递的,所以说在 promiseMiddleware中的 var dispatch = _ref.dispatch 中存储的是

1
2
3
function dispatch() {
return _dispatch.apply(undefined, arguments);
}

这个函数的引用,而在构建过程时,一调用 dispatch 函数,就会调用 _dispatch 函数,然后就报错了。

但是 applyMiddleware 在构建完成后有这样一步操作

1
_dispatch = compose.apply(undefined, chain)(store.dispatch);

这样就覆盖了之前的只会报错的 _dispatch,从而在 middleware 中可以正常的调用 dispatch了。

另外从上面可以看出,==在 middleware 中调用的 dispatch 不是 createStore 的 dispatch,而是经过 middleware 包装的 dispatch==。

上一篇:模块加载原理与 module 源码阅读

下一篇:ES6 之 Symbol

理解 Redux 中间件机制的更多相关文章

  1. 3.3 理解 Redux 中间件(转)

    这一小节会讲解 redux 中间件的原理,为下一节讲解 redux 异步 action 做铺垫,主要内容为: Redux 中间件是什么 使用 Redux 中间件 logger 中间件结构分析 appl ...

  2. redux深入理解之中间件(middleware)

    理解reduce函数 reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值. arr.reduce([callback, initi ...

  3. 理解 Redux 的中间件

    将该思想抽象出来,其实和 Redux 就无关了.问题变成,怎样实现在截获函数的执行,以在其执行前后添加自己的逻辑. 为了演示,我们准备如下的示例代码来模拟 Redux dispatch action ...

  4. DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能

    DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能 一.引言 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作.此时客户会在自己 ...

  5. 【React全家桶入门之十三】Redux中间件与异步action

    在上一篇中我们了解到,更新Redux中状态的流程是这种:action -> reducer -> new state. 文中也讲到.action是一个普通的javascript对象.red ...

  6. 理解Redux以及如何在项目中的使用

    今天我们来聊聊Redux,这篇文章是一个进阶的文章,建议大家先对redux的基础有一定的了解,在这里给大家推荐一下阮一峰老师的文章: http://www.ruanyifeng.com/blog/20 ...

  7. 通俗易懂的理解 Redux(知乎)

    1. React有props和state: props意味着父级分发下来的属性[父组件的state传递给子组件  子组件使用props获取],state意味着组件内部可以自行管理的状态,并且整个Rea ...

  8. Redux 中间件与函数式编程

    为什么需要中间件 接触过 Express 的同学对"中间件"这个名词应该并不陌生.在 Express 中,中间件就是一些用于定制对特定请求的处理过程的函数.作为中间件的函数是相互独 ...

  9. Redux:中间件

    redux中间件概念 比较容易理解. 在使用redux时,改变store state的一个固定套路是调用store.dispatch(action)方法,将action送到reducer中. 所谓中间 ...

随机推荐

  1. 常见的nosql数据库有哪些?以及他们的特点与区别?

    一.常见的nosql 二.Redis,Memcache,MongoDb的特点 (1).Redis 优点: 1.支持多种数据结构,如 string(字符串). list(双向链表).dict(hash表 ...

  2. ae基础一

    1.导入素材2.整理素材3.创建合成1280*720是高清的模式 也是平时都用的格式 HDV/HDTV 720 251920*1080是超清的模式格式是以16:9的格式显示的 电脑电视机都是用这个比例 ...

  3. Adaboost算法及其代码实现

    . . Adaboost算法及其代码实现 算法概述 AdaBoost(adaptive boosting),即自适应提升算法. Boosting 是一类算法的总称,这类算法的特点是通过训练若干弱分类器 ...

  4. VMware12 + Ubuntu16.04 虚拟磁盘扩容

    转载自:https://blog.csdn.net/Timsley/article/details/50742755 今天用虚拟机的时候,发现虚拟机快满了,提示磁盘空间小,不得不扩充虚拟机空间.经过百 ...

  5. requset请求处理与BeanUtils封装

    HTTP: 概念:Hyper Text Transfer Protocol 超文本传输协议 传输协议:定义了,客户端和服务器端通信时,发送数据的格式 特点: 基于TCP/IP的高级协议 默认端口号:8 ...

  6. Android圆角布局、天气应用、树状图、日食动画、仿饿了么导航效果等源码

    Android精选源码 Android通用圆角布局源码 Android天气应用源码,界面美观 一个支持定制的树状 Android 自定义View PIN 码专用输入控件,支持任意长度和输入任意数据 A ...

  7. flutter 命令卡主的问题

    情况 1 镜像的问题 如果你的镜像已经设置,却仍然卡主,那么请参考情况 2 这种情况在中文官网上已经有了,并且有这修改镜像的方法,附上链接: https://flutter.cn/community/ ...

  8. Codeforces Round #316 (Div. 2) D计算在一棵子树内某高度的节点

    题:https://codeforces.com/contest/570/problem/D 题意:给定一个以11为根的n个节点的树,每个点上有一个字母(a~z),每个点的深度定义为该节点到11号节点 ...

  9. P1010 幂次方 P1022 计算器的改良

    P1010 幂次方 一.题目 https://www.luogu.org/problemnew/show/P1010 二.代码 #include<bits/stdc++.h> using ...

  10. Python_运维中常用的20个库和模块

    1.psutil是一个跨平台库(https://github.com/giampaolo/psutil)能够实现获取系统运行的进程和系统利用率(内存,CPU,磁盘,网络等),主要用于系统监控,分析和系 ...