React/Redux项目结束后,当我在研究react-router源码的时候发现当中有一部分含中间件的思想,因此才想把中间件从新梳理一遍;在以前看redux了解到中间件,redux层面中间件的理解对项目前期比较有帮助,虽然项目中后期基本能够忽略这层概念;如今对这部分的笔记从新梳理,这里只针对这个中间件作一个理解。react
若是想学习项目的底层建设,建议先去学习官网redux案例,以后在学习react-router的使用git
Redux
目的是提供第三方插件的模式,改变action -> reducer
的过程。变为 action -> middlewares -> reducer
。本身在项目中使用它改变数据流,实现异步 action
;下面会对日志输出作一个开场。github
Redux 中 applyMiddleware
的方法,能够应用多个中间件,这里先只写一个中间件,以日志输出中间件为例ajax
//利用中间件作打印log import {createStore,applyMiddleware} from 'redux'; import logger from '../api/logger'; import rootReducer from '../reducer/rootReducer'; let createStoreWithMiddleware = applyMiddleware(logger)(createStore); let store = createStoreWithMiddleware(rootReducer); // 也能够直接这样,能够参考createStore // createStore( // rootReducer, // applyMiddleware(logger) // ) export default store;
const logger = store => next => action => { let result = next(action); // 返回的也是一样的action值 console.log('dispatch', action); console.log('nextState', store.getState()); return result; }; export default logger;
store => next => action =>{}
实现了三层函数嵌套,最后返回 next
,给下一个中间件使用,接下来把三层函数拆解;redux
///redux/src/applyMiddleware.js export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, initialState, enhancer) => { var store = createStore(reducer, initialState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
//源码分析 chain = middlewares.map(middleware => middleware(middlewareAPI));
咱们发现store是middlewareAPI,后端
//store var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }
而后就剩下api
next => action => { let result = next(action); // 返回的也是一样的action值 console.log('dispatch', action); console.log('nextState', store.getState()); return result; };
//源码分析 dispatch = compose(...chain)(store.dispatch)
先来分析compose(...chain)promise
//compose源码 export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
compose利用Array.prototype.reduceRight的方法react-router
//reduceRight遍历介绍 [0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) { return previousValue + currentValue; }, 10); //结果 10+4+3+2+1+0 = 20
由于咱们这里的中间件就只有一个,因此没有使用到reduceRight直接返回,直接返回func[0]
(自己);再由compose(...chain)(store.dispatch)
,咱们能够知道next就是store.dispatchapp
(action) => { let result = store.dispatch(action); // 这里的next就是store.dispatch console.log('dispatch', action); console.log('nextState', store.getState()); return result; };
咱们以后调用的dispath
就是触发的是上面这个函数(这里就单个中间件);
经过上面的 applyMiddleware
, compose
和中间件的结构,
假设应用了以下的中间件: [A, B, C],这里咱们使用es5的结构作分析
分析action触发的完整流程
三个中间件
//A function A(store) { return function A(next) { return function A(action) { /*...*/; next(action); /*...*/; return /*...*/; } } } //B function B(store) { return function B(next) { return function B(action) { /*...*/; next(action); /*...*/; return /*...*/; } } } //C function C(store) { return function C(next) { return function C(action) { /*...*/; next(action); /*...*/; return /*...*/; } } }
经过chain = middlewares.map(middleware => middleware(middlewareAPI))
,三个中间件的状态变化
//A function A(next) { return function A(action) { /*...*/; next(action); /*...*/; return /*...*/; } } //B function B(next) { return function B(action) { /*...*/; next(action); /*...*/; return /*...*/; } } //C function C(next) { return function C(action) { /*...*/; next(action); /*...*/; return /*...*/; } }
再由dispatch = compose(...chain)(store.dispatch)
,咱们转化下
const last = C; const rest = [A,B] dispatch = rest.reduceRight( (composed, f) =>{ return f(composed) }, last(store.dispatch) )
咱们获得的结果
dispatch = A(B(C(store.dispatch)));
进一步分析,咱们获得的结果
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,才能够继续作操做,和平时直接异步回调又有些不同
咱们知道redux中actions分为actionType,actionCreator,而后在由reducer进行修改数据;
官方例子中async直接在actionCreator作了ajax请求;
咱们把ajax放入中间件触发下面要讲的与官方real-world相似
我这边使用redux-thunk
applyMiddleware(reduxThunk, api)
先来看看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;
这样一来咱们能够把异步写成一个复用的actionCreator;
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';
正常的写法,不是异步的,就是以前的写法
export function cartSelect(id) { return { type: types.CART_MAIN_SELECT, id }; }
而后就是下一个中间件的处理 api.js
//本身封装的ajax,能够使用别的,好比isomorphic-fetch import net from 'net'; //项目中所有的接口,至关于一个关于异步的actionType有一个对应的后端接口 import API_ROOT from 'apiRoot'; export default store => next => action => { let API_OPT = action['API']; if (!API_OPT) { //咱们约定这个没声明,就不是咱们设计的异步action,执行下一个中间件 return next(action); } let ACTION_TYPE = action['type']; let { apiName, params = {} , opts = {} } = API_OPT; /** * 若是有传递localData,就不会触发ajax了,直接触发_success * 当前也能够传其余参数 */ let { localData } = opts; let { onSuccess, onError, onProgress, ajaxType = 'GET', param } = params; // 触发下一个action let nextAction = function(type, param, opts) { action['type'] = type; action['opts'] = opts; delete param['onSuccess']; delete param['onError']; const nextRequestAction = {...action,...param} return nextRequestAction; }; params={ ...params, data: null }; // 触发正在请求的action let result = next(nextAction(apiName + '_ON', params, opts)); net.ajax({ url: API_ROOT[apiName], type: ajaxType, param, localData, success: data => { onSuccess && onSuccess(data); params={ ...params, data }; //触发请求成功的action return next(nextAction(apiName + '_SUCCESS', params, opts)); }, error: data => { onError && onError(data); //触发请求失败的action return next(nextAction(apiName + '_ERROR', params, opts)); } }); return result; };
强调一点:项目中所有的接口,至关于一个关于异步的actionType有一个对应的后端接口,因此咱们才能够经过API_ROOT[apiName]找到这个接口
以cart为列子(下面是对应的每一个文件):
actionType:
//异步 export const CART_MAIN_GET = 'CART_MAIN_GET'; //非异步 export const CART_MAIN_SELECT = 'CART_MAIN_SELECT';
api:
const api = { 'CART_MAIN_GET':'/shopping-cart/show-shopping-cart' }; export default api;
APIROOT修改:
import cart from './api/cart'; const APIROOT = { ...cart }; export default API;
actionCreator:
//项目中使用redux的bindActionCreators作一个统一的绑定,因此在这里单独引入 export { request } from './request'; //下面是非异步的方法 export function cartSelect(id) { return { type: types.CART_MAIN_SELECT, id }; }
项目中发起结构是这样的:
let url = types.CART_MAIN_GET; let param = {}; let params = { param: param, ajaxType: 'GET', onSuccess: (res) => { /*...*/ }, onError: (res) => { /*...*/ } }; request(url, params, {});
其对应的reducers就是下面
import * as types from '../constants/actions/cart'; const initialState = { main:{ isFetching: 0,//是否已经获取 didInvalidate:1,//是否失效 itemArr:[],//自定义模版 itemObj:{},//自定义模版数据 header:{}//头部导航 } }; export default function(state = initialState, action) { let newState; switch (action.type) { case types.HOME_MAIN_GET + '_ON'://能够不写 /*...*/ return newState; case types.HOME_MAIN_GET + '_SUCCESS': /*...*/ return newState; case types.HOME_MAIN_GET + '_ERROR'://能够不写 /*...*/ return newState; default: return state; } };
异步,数据验证均可以经过中间件作处理;引用Generator,Async/Await,Promise处理,能够参考社区中的一些其余方式,好比: