redux@4.0.0
const logger = ({ dispatch, getState }) => next => action => {
console.log("logger will dispatch");
// 调用 middleware 链中下一个 middleware 的 dispatch。
let returnValue = next(action);
console.log("logger state after dispatch");
return returnValue;
};
/* 最后面的函数是自己的dispatch
next是别人的dispatch
next就是包装后的dispatch就是个函数,然后返回一个新的dispatch函数跟高阶组件一
样,一个是函数接收一个组件返回包装后的新组件,这边中间件就是接收个函数返回个新函数 */
const thunk = ({ dispatch, getState }) => next => action => {
console.info("thunk");
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
const compose = (...funcs) => {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((pre, cur) => {
// console.info("xx");
// console.info(pre.toString());
// console.info(cur.toString());
const func = (...args) => pre(cur(...args));
console.info(func.toString());
return func;
});
};
// 组合后的代码
/* (...args) => thunk()(logger()(...args));
thunk()(logger()(dispatch)); */
/* logger()(dispatch) 返回的是最里层的函数,
该函数作为参数传递给了thunk的next参数,thunk执行完了返回的也是
最后一个函数,这个函数最后被export出去,
在UI里dispatch的时候其实直接执行的是thunk返回的最后的函数,
因为闭包的原因,next会保存在该函数中,调用next的话,其实就是在调用
logger最后的函数,然后logger里再调用 next(action) 这个时候
next就是redux没有经过自定义包装的原生的dispatch,
这就是执行本文代码的时候,为什么会打印顺序会如下的原因
thunk
logger will dispatch
final args
logger state after dispatch */
const applyMiddleware = (...middlewares) => {
let dispatch = () => {
/* throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
); */
console.info("final args");
};
const middlewareAPI = {
getState: () => ({ state: {} }),
dispatch: (...args) => dispatch(...args)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(dispatch);
return dispatch;
};
const dispatch = applyMiddleware(thunk, logger);
// console.dir(dispatch);
dispatch({ type: "test", payload: { name: "geek" } });
logger()(dispatch) 返回的是最里层的函数,该函数作为参数传递给了thunk的next参数,thunk执行完了返回的也是最后一个函数,这个函数最后被export出去,在UI里dispatch的时候其实直接执行的是thunk返回的最后的函数,因为闭包的原因,next会保存在该函数中,调用next的话,其实就是在调用logger最后的函数,然logger里再调用 next(action) 这个时候next就是redux没有经过自定义包装的原生的dispatch,这就是执行本文代码的时候,为什么会打印顺序会如下的原因
thunk
logger will dispatch
final args
logger state after dispatch
也就是说我们在UI里dispatch的时候执行的顺序是:thunk的最后的函数=>logger最后的函数=>redux的dispatch
但是问题来了,redux的dispatch其实是经过包装的
import compose from './compose'
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)//后面dispatch其实也是经过compose的函数
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
dispatch: (...args) => dispatch(...args) 后面dispatch其实也是经过compose的函数,再看看我们如何调用thunk的
function makeASandwichWithSecretSauce(dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
}
本来认为我们在thunk里调用的dispatch是redux自身的dispatch,其实不是,从源码可以看出,只要使用了中间件,经过applyMiddleware的处理那么redux的dispatch就经过了处理,所有传出去的dispatch都会经过中间件,那么在thunk dispatch数据的时候,理所当然再去走所有的中间件,直到走到redux createStore中的dispatch函数把payload数据装载到reducer函数currentState = currentReducer(currentState, action);
这样一条异步数据请求到改变redux的state就走完了
上面的redux-ui的node_modules下也有redux 断点打在了这里的createStore的dispatch函数,一直没执行,所以还是需要把断点打在最外面的redux上就行了,中间F11还会进入VM43360貌似是浏览器内置的函数,不知道为什么,路漫漫其修远兮
我项目里applyMiddleware(thunk, routerMiddleware(history))
那么在所有的dispatch的时候都会经过这两个中间件,如果任意中间件中做了判断没有执行下个next那么该最后就不会执行redux createStore中的dispatch,从而不能调用reducer,那么redux state就不会改变
比如thunk
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
// 此处被拦截 next不会执行,createStore中的dispatch就不会被执行,
// redux state就不会改变,也就是dispatch被拦截了
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
如果dispatch一个普通的数据,那么流程就是thunk=>routerMiddleware=>redux createStore dispatch
-
从函数组合看middleware
柯里化重要的作用是用于函数组合
一个合格的中级前端工程师必须要掌握的 28 个 JavaScript 技巧提到
函数式编程另一个重要的函数 compose,能够将函数进行组合,而组合的函数只接受一个参数,所以如果有接受多个函数的需求并且需要用到 compose 进行函数组合,就需要使用柯里化对准备组合的函数进行部分求值,让它始终只接受一个参数
简化applyMiddleware.js
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;
const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
function applyMiddleware(...middlewares) {
const store = {
dispatch: () => {
console.info("dispatch");
}
};
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
console.dirxml(chain);
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
}
applyMiddleware(thunk, logger);
从上图可以看出chain就是下面的这种结构
const logger = next => action => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
const thunk = next => action => {
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);//thunk 执行到这里在redux里的操作就终止了,
// 也就是不会去改变redux里的state,其实是在action函数的内部使用dispatch去修改state,
// 为什么要return action(dispatch, getState, extraArgument)
// 这样在调用diapatch thunk的时候可以直接拿到异步操作的返回值
}
return next(action);
};
然后compose,得到
logger(thunk(dispatch));
被组合的函数logger 和thunk是参数为next的单参数函数,同时返回一个函数作为下个middleware的参数,结合函数组合被组合的函数的参数肯定只有一个,被组合的函数的返回值作为下个函数的参数 那么应该跟传入的参数的数据类型是一样的,比如参数是number那么返回值也是number,参数是function,返回值也是function。
再看看redux的思想,改变state只有一种方式,那就是dispatch,但是redux默认不支持异步操作,这个时候就需要middleware来辅助,dispatch进行拦截。next就是上一个action=>{}, next和action=>{},就是redux原生定义的dispatch的结构,再看最后得到的dispatch