compose中间件函数JS数组Reduce方法详解
前言
提到中间件,你可能会想到Express
和Koa
等服务端框架,没想到也没关系,这句话是我装逼用的。
那么redux中的中间件到底干嘛用的?
有这样一个问题?我们之前用的Redux
都是在Action
发出之后立即执行Reducer
,计算出state
,这是同步操作。如果想异步操作呢?即过一段时间再执行Reducer
怎么办?这里就需要用到中间件middleware
。
先放一张图看看:
一、中间件的概念
redux
是有流程的,那么,我们该把这个异步操作放在哪个环节比较合适呢?
Reducer
?纯函数只承担计算State
功能,不适合其它功能。View
?与State
一一对应,可以看做是State
的视觉层,也不适合承担其它功能。Action
?它是一个对象,即存储动作的载体,只能被操作。
其实,也只有dispatch
能胜任此重任了。那么怎么在dispatch
中添加其它操作呢?
let next = store.dispatch;
store.dispatch = function(action){
console.log('老状态 ',store.getState());
next(action);
console.log('新状态 ',store.getState());
}
示例中可以看出,我们对store.dispatch
重新进行了定义,在发送action
的前后,做了打印。
这是中间件的大致雏形,真实的中间件要比这么复杂多了
二、中间件的用法
我们在这里先看看中间件是怎么使用,下面我们一步步剖析每个细节。
import {applyMiddleware,createStore} from 'redux';
import reduxLogger form 'redux-logger';
const store = createStore(reducer,inital_state,applyMiddleware(thunk, promise,reduxLogger));
代码中有两点需要注意:
1、
createStore
方法可以整个应用的初始状态作为参数
内部是这么处理的let state = inital_state;
- 2、中间件的参数次序有讲究。下面我会把这个问题讲明白。
三、applyMiddleware
Middleware可以让你包装store
的dispatch
方法来达到你想要的目的。同时,middleWare
还拥有“可组合”这一关键特性。多个middleWare
可以被组合到一起使用,形成middleWare
链,依次执行。其中每个middleware
不需要关心链前后的的middleWare
的任何信息。
function applyMiddleware(...middlewares){
returnfunction(createStore){
returnfunction(reducer){
//引入store
let store = createStore(reducer);
let dispatch = store.dispatch;
let middlewareAPI = {
getState:store.getState,
// 对dispatch进行包装
dispatch:action=>dispatch(action)
}
//每个中间件都是这种模型 ({ getState, dispatch }) => next => action
chain = middlewares.map(middleware=>middleware(middleAPI));
dispatch = compose(...chain)(store.dispatch);
// dispatch被改装后,返回store
return{...store,dispatch};
}
}
}
上面代码中,所有中间件都被放进了一个数组chain
,然后嵌套执行,最后执行store.dispatch
。中间件内部middlewaAPI
可以拿到getState
和dispatch
这两个方法。
...middleware
:遵循Redux middleware API
的函数。每个middleware
接受Store
的dispatch
和getState
函数作为命名参数,并返回一个函数。该函数会被传入成为next
的下一个middleWare 的dispatch方法,并返回一个接收action的新函数,这个函数可以直接调用next(action),或者在其他需要的时刻调用,甚至根本不去调用它。
所以,接下来,我们就能看到middleware的函数签名是({ getState, dispatch }) => next => action
其实,它的本质就是包装sotre中的dispatch
。
上面代码中,还用到了compose
方法,我们来看看compose是怎么是实现的?
compose
先看下面一个栗子:
function add1(str){
return str+1;
}
function add2(str){
return str+2;
}
function add3(str){
return str+3;
}
let result = add3(add2(add1('好吃')));// 好吃123;
这中写法调用起来,一层套一层,是不是看着很不爽,我们简化一下:
function compose(...fns){
if(fns.length==1)
return fns[0];
returnfunction(...args){
let last = fns.pop();
return fns.reduceRight((prev,next)=>{
return next(prev);
},last(...args));
}
}
let add = compose(add3,add2,add1);//
let result = add('好吃');// 好吃123
// 上面的代码其实就是redux3.6.0版本中compose的实现方式
看看这个代码是不是用起来,很干练一些。其实还可以简化
function compose(...fns){
if(fns.length==1)
return fns[0];
return fns.reduce((a,b)=>(...args)=>a(b(...args)));//add3(add2(add1('好吃')))
}
let add = compose(add3,add2,add1);//
let result = add('好吃');// 好吃123
// 这是redux3.6.0版本之后的compose实现方式,一直沿用至今。
至于为什么applyMiddleWare
的参数有顺序,这里给出了答案。
四、Applymiddleware的三个常用参数
4.1、日志记录
使用 Redux 的一个益处就是它让 state 的变化过程变的可预知和透明。每当一个 action 发起完成后,新的 state 就会被计算并保存下来。State 不能被自身修改,只能由特定的 action 引起变化。
试想一下,当我们的应用中每一个 action 被发起以及每次新的 state 被计算完成时都将它们记录下来,岂不是很好?当程序出现问题时,我们可以通过查阅日志找出是哪个 action 导致了 state 不正确。
图片的效果是不是很期待啊!!!
我们先来手动实现一版。
// 记录所有被发起的action和新的statelet next = store.dispatch;
store.dispatch = function(action){
console.log('老状态 ',store.getState());
next(action);
console.log('新状态 ',store.getState());
}
还是上面的示例,我们来做个修改
let logger = function({ getState, dispatch }){
returnfunction(next){// 这里的next可以理解为store.dispath,本质上就是调用 middleware 链中下一个 middleware 的 dispatch。returnfunction(action){
console.log('老状态1 ',getState());
next(action);//派发动作console.log('新状态1 ',getState());
}
}
}
// 高逼格写法let logger = ({ getState, dispatch }) => next => action => {
console.log('老状态1 ',getState());
next(action)
console.log('新状态1 ',getState());
}
4.2、redux-thunk 中间件
redux-thunk
是redux
官方文档中用到的异步组件,实质就是一个redux
中间件,一个封装表达式的函数,封装的目的就是延迟执行表达式。
redux-thunk
是一个通用的解决方案,其核心思想是让action
可以变成一个thunk
,这样的话,同步情况:dispatch(action)
,异步情况:dispatch(thunk)
。
下面是redux-thunk
的实现:
let thunk = ({dispatch,getState})=>next=>action=>{
if(typeof action == 'function'){
action(dispatch,getState);
}else{
next(action);//这里可以理解为dispatch(action),本质上就是调用 middleware 链中下一个 middleware 的 dispatch。
}
}
使用redux-thunk
const store = createStore(
reducer,
applyMiddleware(thunk)
);
然后我们实现一个thunkActionCreator
//过一秒加1
exportfunction thunkActionCreator(payload){
returnfunction(dispatch,getState){
setTimeout(function(){
dispatch({type:types.INCREMENT,payload:payload});
},1000);
}
},
最后,在组件中dispatch thunk
this.dispatch(thunkActionCreator(payload));
4.3、redux-promise
redux-promise
也是延迟执行的表达式,它是解决异步的另外一种方案。
redux-thunk
和核心思想是把action
变成thunk
,而redux-promise
的核心思想是让action
返回一个promise对象。
这个中间件使得store.dispatch
方法可以接收Promise对象作为参数。这时 ,action 有两种写法:
写法一、返回值是一个Promise对象。
functionpromiseIncrement(payload){
// return {type:types.INCREMENT,payload:payload} 以前是这种写法returnnewPromise(function(resolve,reject){
setTimeout(function(){
resolve({type:types.INCREMENT,payload:payload});
},1000);
});
},
写法二,action 对象的payload属性是一个Promise对象,这需要从
functionpayloadIncrement(){
return {
type:types.INCREMENT,
payload: newPromise(function(resolve,reject){
setTimeout(function(){
if(Math.random()>.5){
resolve(100);
}else{
reject(-100);
}
},1000)
})
}
}
下面我们来看看 redux-promise
是怎么实现的,就会明白它内部是怎么操作的.
let promise = ({dispatch,getState})=>next=>action=>{
if(action.then && typeof action.then == 'function'){
action.then(dispatch);
// 这里的dispatch就是一个函数,dispatch(action){state:reducer(state,action)};
}elseif(action.payload&& action.payload.then&& typeof action.payload.then == 'function'){
action.payload.then(payload=>dispatch({...action,payload}),payload=>dispatch({...action,payload}));
}else{
next(action);
}
}
上面的代码可以看出,如果Action本身就是一个Promise,它resolve以后的值应该是一个Action对象,会被dispatch方法送出action.then(dispatch);如果Action
对象的 payload
属性是一个Promise
对象,那么无论resolve
和reject
,dispatch 方法都会发出Action
。