记录下自己对redux 中间件的理解,希望可以帮助到你。
applyMiddleware中间件在哪个部分运行
中间件 听名字就能感觉出来 肯定是运行在程序中间
重点是运行在哪个部分中间
这个一个正常的流程
中间件允许我们在dispatch action 之后 到达reducer之前 这个区间 做自己想做的事情(为所欲为≧ω≦)
现在搞明白了他的运行环境,那这个东西到底能做些什么,网上有很多例子 打印啊 报错日志啊 这些都没用过(允许我皮一下),不过异步请求是我们在项目中实实在在需要用到的
使用场景 发起一个action 自动ajax请求 请求成功后发出reducer 更改state 触发更新view 这是一个异步请求的过程
然后我们来结合源码弄清楚他的运行原理 最后再说怎么实现异步请求。
看一下实际项目中运行的代码
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from' ../reducers';
import DevTools from' ../containers/DevTools';
import thunk from 'redux-thunk';
finalCreateStore =compose(
//这是实际项目中用到的场景(拿项目说话,有理有据的样子)
applyMiddleware(thunk, api, reduxRouterMiddleware),
//applyMiddleware里面三个参数 thunk是redux-thunk提供 api 自己封装
//reduxRouterMiddleware可以把router中的location(url信息等)注入到redux中
DevTools.instrument(),
//这个是react调试用的
persistState(getDebugSessionKey())
//这个是react增强调试 切换路由状态将会被清空重置
//主要介绍和中间件相关的 这些无关的简要略过
)(createStore);
const store = finalCreateStore(rootReducer, initialState);
可以看到实际项目中应用的场景是这样子
重新整理下代码 屏蔽那些 本轮不看的代码
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from' ../reducers';
const store = compose(
applyMiddleware(d1, d2, d3),
)(createStore)(rootReducer, initialState);
是的 现在这样是 咱们要说的主要代码
一层层看 compose先是运行一次传递createStore作为参数 内部返回的代码又运行 传递了rootReducer 和 initialState 作为参数
那现在我们来看看 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))).
*/
//在这里其实已经可以看到 compose(f, g, h) 执行结果就是 (...args) => f(g(h(...args)))
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}
compose代码是我现在项目中用的,直接到node_modules中翻出来的
可能和以前版本不一样 网上很多文章 内部代码是用 reduceRight 不管啦以我自己翻到的为主(就是这么霸气 ╮( ̄▽  ̄)╭)
funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
主要的代码可以看见 就是这些 代码不多
funcs.reduce((a, b) => (...args) => a(b(...args)))
可以简写成这样子 没错吧 (es6语法不熟练的同学自行去补补课,reduce就是一个叠加器叠加执行)
举个栗子
var fn1 = val => 'fn1-' + val
var fn2 = val => 'fn2-' + val
var fn3 = val => 'fn3-' + val
compose(fn1,fn2,fn3)('测试')
fn1-fn2-fn3-测试 这是执行结果
第一次执行开始
a = fn1
b = fn2
(...args) => a(b(...args))
(...args) = > fn1(fn2(...args))
第一步执行完 a = (...ag) => fn1(fn2(...ag)) //如果不理解怎么变成这个样子的去看看 reduce 函数
第二步开始执行
a = (...args) => fn1(fn2(...args))
b = fn3
(...args) => a(b(...args))
(...args) => a( fn3(...args) )
(...args) => fn1(fn2(fn3(...args)))
咦咋就变成这样了 a = (...args) => fn1(fn2(...args)) 执行这一步的时候 b(...args)作为了参数传进了a 也就是 ...args = b(...args) = fn3(...args)
最后的运行结果就是 (...args) => fn1(fn2(fn3(...args)))
总结一下compose 就是函数叠加执行内层的执行结果时外层的参数 生成过程是由内向外,执行过程是由外向内
这是咱们从源码解读、代码演示、官方注释、上都能看出来的结果。记住最后面最右面的最先执行 以后 compose 不再说了
compose 说完了
回到咱们最初的代码
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from' ../reducers';
const store = compose(
applyMiddleware(f1, f2, f3),
)(createStore)(rootReducer, initialState);
再看 applyMiddleware 里面到底执行了什么东东
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
也是我从node_modules里翻出来的
代码看上去要比之前解读过得compose长一点 别怕 一点点看
首先可以看到 运行 applyMiddleware 后 会返回一个函数 (高阶函数的用法)需要传递createStore作为参数 然后又返回一个函数 需要传递reducer, preloadedState, enhancer三个值作为参数
感觉比较乱我们用图参照的比较一下就清晰了
很清晰把每一个都对上了
接下来继续看
const store = createStore(reducer, preloadedState, enhancer)
创建了 store 这个对象是 createStore生成的
那再来看下 createStore 内部做了些什么 (可能你有点不习惯,我在探索技术的时候就是一层层往下屡着看,看到不会的就往下面找,直至全部搞懂)
createStore 内部
createStore内部源码就比较多了 咱们不每一行解读了(本来本文说的就挺多createStore如果再每一行过一遍文章太长了)
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
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.');
}
return enhancer(createStore)(reducer, preloadedState);
}
...
}
执行 createStore需要传递三个参数reducer, preloadedState, enhancer 执行后会返回 一个对象
createStore会判断 第二个参数preloadedState是否是个函数并且enhancer 是否是undefined 如果成立
enhancer = preloadedState;
preloadedState = undefined;
然后如果enhancer 是函数 就去执行 传递的参数还是 createStore
这个不多说了 咱们没有传递 enhancer
preloadedState 正常情况就是默认值 也可以不传
执行了之后会返回一个对象
return {
dispatch, // 传入 action,调用 reducer 及触发 subscribe 绑定的监听函数
subscribe, //subscribe 这个函数是用来去订阅 store 的变化
getState, //获取最新的State
replaceReducer, // 用新的 reducer 代替当前的 reducer,使用不多
[$$observable]: observable
}
(其实除了 dispatch 其他的你基本上用不到 dispatch 也是就几处用到,因为 bindActionCreators帮我们隐形调用了dispatch(action)这里有机会咱们以后详细说)
搞定了 createStore的返回值下面的代码就能看懂了
回头继续看applyMiddleware
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
创建了 chain 数组
创建 middlewareAPI 里面 有getState 和 dispatch
chain = middlewares.map(middleware => middleware(middlewareAPI))
先把所有的中间件 带着 middlewareAPI 运行一遍 保存到chain 中
dispatch = compose(...chain)(store.dispatch)
又看见了compose
很好 之前说了 compose 是函数叠加执行
执行刚刚 带着middlewareAPI 执行了一遍返回的函数集合chain
再传入store.dispatch作为参数 (是最后一个函数的参数呦)
代入代码咱们看一眼
const store = compose(
applyMiddleware(f1, f2, f3),
)(createStore)(rootReducer, initialState);
//这是之前的例子
//chain是个数组 里面的内容就是每一个middleware带着middlewareAPI执行了一遍
//现在先不要管f1 f2 f3内部是如何执行的
//chain内的执行结果作为参数传入compose 在让他们叠加执行(就是把每个middleware串联起来)并赋值给dispatch
//dispatch = f1(f2(f3(store.dispatch)))) 这是 compose执行后的样子
//现在可以看到 执行 dispatch 就是执行 f1(f2(f3(store.dispatch)))) 也就是每个 middleware依次执行
//中间件的编写格式
const f1 = store => next => action => {
next(action)
}
const f2 = store => next => action => {
action = action.type + '_SUCCESS'
next(action)
}
const f3 = store => next => action => {
next(action)
}
三个箭头 第一次看感觉头都大
别怕在画一个图 就清晰了
这回清楚了吗
chain = middlewares.map(middleware => middleware(middlewareAPI))
middleware 的第一个参数store 是middlewareAPI 里面有getState 和 dispatch
dispatch = compose(...chain)(store.dispatch)
middleware 的第二个参数next是store.dispatch
看到这可能你就有疑问了 多么语义化的参数 next 以我小学三年级的英语水平 一眼看出这是下一个的意思 可是下一个为什么 是 store.dispatch 很不合逻辑啊
在看下 compose的函数串联
f1(f2(f3(store.dispatch))))
仔细看
f1的next是 f2
f2的next 是 f3
f3的next 是store.dispatch
之前我也强调过 store.dispatch 只是最后一个函数的参数 没有理解的同学在仔细品味品味
最后一个action参数 应该都了解 这是dispatch执行时咱们自己加的 里面有个type值对应reducer 触发更改state用的
再回到现实项目看看 (就是这样,很真实,很落地)
applyMiddleware(thunk, api, reduxRouterMiddleware)
thunk 是 redux-thunk 提供的
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
内部的源码是这样
其实thunk 内部就是 判断你发起的action是不是函数 是函数就把dispatch, getState 作为参数传给你 不是函数就只交给下一个 next(action)
有的项目是直接把异步请求放到了actionCreator 里 这样 action是个函数 请求回来又可以触发dispacth
我所做的项目不是用的这种形式,在actionCreator中写了一个函数用于动态的创建action
import * as types from '../../constants/actions/common';
export function request(apiName, params, opts = {}) {
return (dispatch, getState) => {
let action = {
'API': {
apiName: apiName,
params: params,
opts: opts
},
type: types.API_REQUEST
};
return dispatch(action);
};
}
//其他地方调用复用的方法如下:
export { request } from './request';
是在thunk后面又自己加了个中间件 api 然后再 这里可以做更多的控制 请求中的action 成功的 失败的 请求拦截的
export default store => next => action => {
let API_OPT = action['API'];
if (!API_OPT) {
//我们约定这个没声明,就不是我们设计的异步action,执行下一个中间件
return next(action);
}
在这里可以开启你的真正的逻辑了
...
};
基本上到这里就是全部内容了 讲解了 createStore, applyMiddleware, compose,thunk
我当初在看这里的时候也不是一下全能吸收的,尤其是next(action)这里
如果你的next 这里同样有和我一样的疑惑
function col(val){
console.log(val)
console.log('执行')
}
function f1(next){
console.log('f1')
return function(action){
console.log(action,'f1Action')
next(action/2)
}
}
function f2(next){
console.log('f2')
return function(action){
console.log(action,'f2Action')
next(action+2)
}
}
function f3(next){
console.log('f3')
return function(action){
console.log(action,'f3Action')
next(action+1)
}
}
var dispatch = f1(f2(f3(col)))
dispatch(1)
运行下试试看看
在写这篇文章的时候 也找了很多材料
https://segmentfault.com/a/1190000007843340
这是我看见其中比较好的,好文要推荐,一同学习一同进步