前言
React/Redux專案結束後,當我在研究react-router原始碼的時候發現當中有一部分含中介軟體的思想,所以才想把中介軟體重新梳理一遍;在之前看redux瞭解到中介軟體,redux層面中介軟體的理解對專案前期比較有幫助,雖然專案中後期基本可以忽略這層概念;現在對這部分的筆記重新梳理,這裡只針對這個中介軟體做一個理解。
如果想學習專案的底層建設,建議先去學習官網redux案例,之後在學習react-router的使用
Redux 中介軟體介紹
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 ,給下一個中介軟體使用,接下來把三層函式拆解;
從applyMiddleware原始碼開始分析 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
}
}
}
最外層store
//原始碼分析
chain = middlewares.map(middleware => middleware(middlewareAPI));
我們發現store是middlewareAPI,
//store
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
然後就剩下
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