React/Redux項目結束後,當我在研究react-router源碼的時候發現當中有一部分含中間件的思想,因此纔想把中間件從新梳理一遍;在以前看redux瞭解到中間件,redux層面中間件的理解對項目前期比較有幫助,雖然項目中後期基本能夠忽略這層概念;如今對這部分的筆記從新梳理,這裏只針對這個中間件作一個理解。react
若是想學習項目的底層建設,建議先去學習官網redux案例,以後在學習react-router的使用ajax
Redux 目的是提供第三方插件的模式,改變action -> reducer 的過程。變爲 action -> middlewares -> reducer 。本身在項目中使用它改變數據流,實現異步 action ;下面會對日誌輸出作一個開場。redux
Redux 中 applyMiddleware 的方法,能夠應用多箇中間件,這裏先只寫一箇中間件,以日誌輸出中間件爲例後端
//利用中間件作打印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; logger 中間件結構分析 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 ,給下一個中間件使用,接下來把三層函數拆解;api
從applyMiddleware源碼開始分析 redux/src/applyMiddleware.jspromise
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 } } }
最外層store
//源碼分析react-router
chain = middlewares.map(middleware => middleware(middlewareAPI));
咱們發現store是middlewareAPI,app
//store異步
var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }
而後就剩下async
next => action => { let result = next(action); // 返回的也是一樣的action值 console.log('dispatch', action); console.log('nextState', store.getState()); return result; };
中間層next
//源碼分析
dispatch = compose(...chain)(store.dispatch)
先來分析compose(...chain)
//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的方法
//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.dispatch
(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處理,能夠參考社區中的一些其餘方式,好比:
redux-promise
redux-saga