1.中间件怎样在react项目中触发执行与使用
只有发送 action 的这个步骤,即store.dispatch()方法时,才会触发
- import { applyMiddleware } from "redux";
- const store = createStore(reducers,applyMiddleware(logger));
- applyMiddleware(…… , ……) 可以依次放入项目所需用到的中间件
2.createStore()分析
const store = createStore(reducer, preloadedState, enhancer)
reducer
:即一些改变state的纯函数preloadesState
: 是初始化的state;接受整个应用的初始状态作为参数enhancer
: store增强功能,在redux中特指applyMiddleware()方法的返回值;一般情况下 createStore 的第三个参数 enhancer 就是 applyMiddlewave- 创建store,返回几个函数,主要是dispatch,getState,subscribe,replaceReducer
上源码
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)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
复制代码
3.一个简易logger中间件的实现示例
// 一个logger中间件
const logger = store => next => action =>{
console.log('prevstate',store.getState())
console.log('dispatch',action);
let result = next(action);
console.log('nextstate',store.getState());
return result;
}
function A(store) {
return function A(next) {
return function A(action) {
/*...*/;
next(action);
/*...*/;
return /*...*/;
}
}
}
export default logger;
复制代码
- 中间件是一个三阶函数,看他要执行几次才能到最内部的函数代码,就是几阶,统称就是高阶函数。
- 三层函数包装:第一层传入store(因此可以使用store的方法),第二层传入下一个中间件next,第三层传入此次执行的action。实现了三层函数嵌套,最后返回 next
- 在最内层的函数实现中,需要调用next中间件,并返回结果。
4.applyMiddleware(...middlewares) 源码分析
作用是将所有中间件组成一个数组,依次执行
所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法
//applyMiddleware 源码
(middlewares) => (createStore) => (reducer, preloadedState) => {
// 第一步先创建一个store
var store = createStore(reducer, preloadedState, enhancer)
// 缓存dispatch,原store的dispatch要改写。
var dispatch = store.dispatch
// 定义chain来存放 执行后的二阶中间件
var chain = []
// middleware 柯理化的第一个参数。参照logger1的store,这里只保留getState,和改造后的dispatch两个方法。
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 把中间件处理一层,把getState,dispatch方法传进去,也就是中间件柯理化第一次的store参数。
// 这样能保证每个中间件的store都是同一个,柯理化的用途就是预置参数嘛。
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 串联起所有的中间件,dispatch重新赋值,这样调用dispatch的时候,就会穿过所有的中间件。
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
复制代码
applyMiddleware() 方法
- applyMiddleware 一个三阶函数,是用来改写store的dispatch方法,并把所有的中间件都compose串联起来,通过改写dispatch,来实现redux功能的拓展。
- 接收一个middleware数组作为参数,返回一个方法,就是enhancer;
- enhancer仍然是一个方法,他接收createStore方法作为参数,enhancer方法返回的还是一个方法,这个方法的参数与createStore方法一样,都包含三个参数,分别是reducer, preloadedState, enhancer;
- 这个最终的方法的作用有2点,首先调用createStore创建store,然后就是不断的替换store.dispatch,以达到应用中间件的目的;最终就是把原始的store.dispatch应用到所有的中间件的最核心的位置调用,其他的插件都在原始的dispatch外层包裹着。
- applyMiddlewave 依然使用 createStore 创建了store 对象并且返回,只是改写了这个对象的 dispatch 方法。
默认的redux流程
view --> dispatch --> action --> reducer --> render --> view
applyMiddleware封装之后
View -> ( mid1 -> mid2 -> …… …… ) -> reducer --> render --> view
但是经过applyMiddleware的包装,store里面的被封装,在调动action之后,执行封装后的dispatch就会经过一系列的中间件处理,再去触发reducer。
复制代码
中间件的运行原理
- 将原生的 getState 和 dispacth 作为第一个参数传入中间件数组,获得执行完的 chain 数组:
chain = middlewares.map(middleware => middleware(middlewareAPI)) - 组合串联 middleware:
dispatch = compose(...chain)(store.dispatch)
compose 将所有的中间件串联起来组成新的 dispatch
何谓柯理化---- 把一个需要传入多个变量的函数变为多个嵌套函数,并且内层函数会调用上层函数的变量。
柯理化函数的核心是闭包,因为闭包,所以内层函数才能够保留父作用域中的变量。
function text1(x, y) {
return x * y;
}
console.log(add(2, 3)); // 6
function text1(x) {
return function (y) {
return x * y;
}
}
console.log(add2(2)(3)); // 6
复制代码
5.compose源码分析
//compose
其实compose是函数式编程中比较重要的一个方法。上面调用compose的时候可见是一个二阶函数。
const compose = (...funcs) => {
//没有参数,那就返回一个function
if (!funcs.length) {
return arg => arg
}
//一个中间件,返回它
if (funcs.length === 1) {
return funcs[0];
}
// 最后一个
var last = funcs[funcs.length -1];
// 复制一份,除去last
var rest = funcs.slice(0, -1);
// 返回函数,可以接收store.dispatch。
// reduceRight 反向迭代。
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
//reduceRight遍历介绍
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
return previousValue + currentValue;
}, 10);
//结果 10+4+3+2+1+0 = 20
复制代码
1 通过chain = middlewares.map(middleware => middleware(middlewareAPI)),得到以下三个中间件的状态变化:
//A
function A(next) {
return function A(action) {
/*...*/;
next(action);
/*...*/;
return /*...*/;
}
}
// B ……
// C ……
2 再由dispatch = compose(...chain)(store.dispatch),我们转化下
const last = C;
const rest = [A,B]
dispatch = rest.reduceRight(
(composed, f) =>{
return f(composed)
},
last(store.dispatch)
)
3 得到的结果
dispatch = A(B(C(store.dispatch)));
4 进一步分析,我们得到的结果:
dispatch = A(B(C(store.dispatch)));
//执行C(next),得到结果
A(B(function C(action) {/*...*/;next(action);/*...*/;return /*...*/;}));
//此时的next = store.dispatch
//继续执行B(next)
A(function B(action) {/*...*/;next(action);/*...*/;return /*...*/;});
//此时的next = function C(action) {/*...*/;next(action);/*...*/;return /*...*/;}
//继续执行A(next)
function A(action) {/*...*/;next(action);/*...*/;return /*...*/;};
//此时的next = function B(action) {/*...*/;next(action);/*...*/;return /*...*/;}
复制代码
一个action触发执行顺序,A(action) -> B(action) -> C(action) -> store.dispatch(action)(生产最新的 store 数据); 如果next(action)下面还有需要执行的代码,继续执行 C(next 后的代码)->B(next 后的代码)->A(next 后的代码)
总结:先从内到外生成新的func,然后由外向内执行。本来我们可以直接使用store.dispatch(action),但是我们可以通过中间件对action做一些处理或转换,比如异步操作,异步回调后再执行next;这样的设计很巧妙,只有等待next,才可以继续做操作,和平时直接异步回调又有些不一样
- 参考我们的 logger middlewave 这里的 composed 即是我们的 next 参数。
- reduceRight 和 ruduce 一样,不过 reduceRight 是从数组的右端开始执行,arg 将作为 reduceRight 的初始值(这里就是 store.dispatch)。假设我们的 chain 数组为 [f1,f2,f3]执行完毕后 dispatch 为 dispatch = f1(f2(f3(store.dispatch)))),调用这个新的 dispatch 每个中间件就能依次执行了,只有最后一个中间件会触发 redux 原生的 dispatch,将这个 action 分发出去
- 在applyMiddleware 中 chain数组中都是已经预置middlewareAPI参数后的二阶函数。执行传入的参数都是 形参next。
- 通过执行compose(...chain)(store.dispatch),last是最后一个中间件,执行并传入 store.dispatch, 返回一个只剩一阶的(action) => {}, 不过已经预置了next参数,也就是store.dispatch
- 然后last(...args)返回的结果传入reduceRight的回调, 对应形参是composed。
- f是rest的最后一项, 执行并把 composed 传入,等同于f形参中的next... 得到的结果也是一阶函数,预置的next是last(...args) ...
- 以此类推。这样,就形成了一个嵌套多层的语句。 类似于logger1(logger2(store.dispatch),当然这只是一个比喻。 只不过到第一个middleware的时候,是二阶函数传入next执行,得到一阶函数返回赋值给dispatch,这时的一阶函数已经变成了形似这样:
function (action) {
console.log('logger1 start', action);
next(action);
console.log('logger1 end', action);
}
复制代码
经过compose之后的dispatch执行
- 返回的store中dispatch被修改,执行store.dispatch的时候,也就是这个函数执行.
- 当执行到next(action)的时候,会调用已经预置的next函数,也就是第二个中间件的(action) => {},依次类推。直到最后一个中间件,他的next函数是store.dispatch函数,执行并把action传入。
- 执行完最后一个中间件的next(action),也就是初始的dispatch。next后面的代码再执行,再逆向把中间件走一遍,直到第一个中间件执行完毕。就会出现这种效果
start logger1 Object {type: "ADD_TODO", text: "defaultText"}
start logger2 Object {type: "ADD_TODO", text: "defaultText"}
dispatch()
end logger2 Object {type: "ADD_TODO", text: "defaultText"}
end logger1 Object {type: "ADD_TODO", text: "defaultText"}
dispatch(actions) --> m1 --> next --> m2 --> next --> m3 --> (store.dispatch(action)) --> m3 --> m2 --> m1
复制代码
6. 中间件的次序也有讲究,有些时候有次序要求
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger)
);
复制代码
比如,logger(打印日志的中间件)就一定要放在最后,否则输出结果会不正确。
[本文参考地址来自xiexin大佬]juejin.im/post/5a23b1…)
[本文参考地址来自誑逩蝸犇大佬]www.jianshu.com/p/dbe65d95c…)
[本文参考地址来自sunyongjian大佬]github.com/sunyongjian…)
[本文参考地址来自Deot大佬]segmentfault.com/a/119000000…)
[本文参考地址来自yuanyuanispeak大佬]blog.youkuaiyun.com/yuanyuanisp…)
[本文参考地址来自刘一奇大佬]www.liuyiqi.cn/2016/02/02/…)
结语
前端react QQ群:
788023830
----React/Redux - 地下老英雄
前端交流 QQ群:
249620372
----FRONT-END-JS前端
(我们的宗旨是,为了加班,为了秃顶……,仰望大佬),希望小伙伴们加群一起学习