摘要:如果想學(xué)習(xí)項(xiàng)目的底層建設(shè),建議先去學(xué)習(xí)官網(wǎng)案例,之后在學(xué)習(xí)的使用中間件介紹目的是提供第三方插件的模式,改變的過(guò)程。
前言
React/Redux項(xiàng)目結(jié)束后,當(dāng)我在研究react-router源碼的時(shí)候發(fā)現(xiàn)當(dāng)中有一部分含中間件的思想,所以才想把中間件重新梳理一遍;在之前看redux了解到中間件,redux層面中間件的理解對(duì)項(xiàng)目前期比較有幫助,雖然項(xiàng)目中后期基本可以忽略這層概念;現(xiàn)在對(duì)這部分的筆記重新梳理,這里只針對(duì)這個(gè)中間件做一個(gè)理解。
Redux 中間件介紹如果想學(xué)習(xí)項(xiàng)目的底層建設(shè),建議先去學(xué)習(xí)官網(wǎng)redux案例,之后在學(xué)習(xí)react-router的使用
Redux 目的是提供第三方插件的模式,改變action -> reducer 的過(guò)程。變?yōu)?action -> middlewares -> reducer 。自己在項(xiàng)目中使用它改變數(shù)據(jù)流,實(shí)現(xiàn)異步 action ;下面會(huì)對(duì)日志輸出做一個(gè)開(kāi)場(chǎng)。
使用 Redux 中間件Redux 中 applyMiddleware 的方法,可以應(yīng)用多個(gè)中間件,這里先只寫(xiě)一個(gè)中間件,以日志輸出中間件為例
//利用中間件做打印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 中間件結(jié)構(gòu)分析
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 =>{} 實(shí)現(xiàn)了三層函數(shù)嵌套,最后返回 next ,給下一個(gè)中間件使用,接下來(lái)把三層函數(shù)拆解;
從applyMiddleware源碼開(kāi)始分析///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));
我們發(fā)現(xiàn)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; };
//源碼分析 dispatch = compose(...chain)(store.dispatch)
先來(lái)分析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); //結(jié)果 10+4+3+2+1+0 = 20
因?yàn)槲覀冞@里的中間件就只有一個(gè),所以沒(méi)有使用到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; };
我們之后調(diào)用的dispath就是觸發(fā)的是上面這個(gè)函數(shù)(這里就單個(gè)中間件);
多個(gè)中間件通過(guò)上面的 applyMiddleware , compose 和中間件的結(jié)構(gòu),
假設(shè)應(yīng)用了如下的中間件: [A, B, C],這里我們使用es5的結(jié)構(gòu)做分析
分析action觸發(fā)的完整流程
三個(gè)中間件
//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 /*...*/; } } }
通過(guò)chain = middlewares.map(middleware => middleware(middlewareAPI)),三個(gè)中間件的狀態(tài)變化
//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),我們轉(zhuǎn)化下
const last = C; const rest = [A,B] dispatch = rest.reduceRight( (composed, f) =>{ return f(composed) }, last(store.dispatch) )
我們得到的結(jié)果
dispatch = A(B(C(store.dispatch)));
進(jìn)一步分析,我們得到的結(jié)果
dispatch = A(B(C(store.dispatch))); //執(zhí)行C(next),得到結(jié)果 A(B(function C(action) {/*...*/;next(action);/*...*/;return /*...*/;})); //此時(shí)的next = store.dispatch //繼續(xù)執(zhí)行B(next) A(function B(action) {/*...*/;next(action);/*...*/;return /*...*/;}); //此時(shí)的next = function C(action) {/*...*/;next(action);/*...*/;return /*...*/;} //繼續(xù)執(zhí)行A(next) function A(action) {/*...*/;next(action);/*...*/;return /*...*/;}; //此時(shí)的next = function B(action) {/*...*/;next(action);/*...*/;return /*...*/;}
一個(gè)action觸發(fā)執(zhí)行順序,A(action) -> B(action) -> C(action) -> store.dispatch(action)(生產(chǎn)最新的 store 數(shù)據(jù));
如果next(action)下面還有需要執(zhí)行的代碼,繼續(xù)執(zhí)行 C(next 后的代碼)->B(next 后的代碼)->A(next 后的代碼)
總結(jié):先從內(nèi)到外生成新的func,然后由外向內(nèi)執(zhí)行。本來(lái)我們可以直接使用store.dispatch(action),但是我們可以通過(guò)中間件對(duì)action做一些處理或轉(zhuǎn)換,比如異步操作,異步回調(diào)后再執(zhí)行next;這樣的設(shè)計(jì)很巧妙,只有等待next,才可以繼續(xù)做操作,和平時(shí)直接異步回調(diào)又有些不一樣
項(xiàng)目實(shí)踐 ->異步我們知道redux中actions分為actionType,actionCreator,然后在由reducer進(jìn)行修改數(shù)據(jù);
官方例子中async直接在actionCreator做了ajax請(qǐng)求;
我們把a(bǔ)jax放入中間件觸發(fā)下面要講的與官方real-world類(lèi)似
我這邊使用redux-thunk
applyMiddleware(reduxThunk, api)
先來(lái)看看redux-thunk的源碼
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === "function") {//重新分發(fā) return action(dispatch, getState, extraArgument); } return next(action);//傳遞給下一個(gè)中間件 }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
這樣一來(lái)我們可以把異步寫(xiě)成一個(gè)復(fù)用的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); }; } //其他地方調(diào)用復(fù)用的方法如下: export { request } from "./request";
正常的寫(xiě)法,不是異步的,就是之前的寫(xiě)法
export function cartSelect(id) { return { type: types.CART_MAIN_SELECT, id }; }
然后就是下一個(gè)中間件的處理 api.js
//自己封裝的ajax,可以使用別的,比如isomorphic-fetch import net from "net"; //項(xiàng)目中全部的接口,相當(dāng)于一個(gè)關(guān)于異步的actionType有一個(gè)對(duì)應(yīng)的后端接口 import API_ROOT from "apiRoot"; export default store => next => action => { let API_OPT = action["API"]; if (!API_OPT) { //我們約定這個(gè)沒(méi)聲明,就不是我們?cè)O(shè)計(jì)的異步action,執(zhí)行下一個(gè)中間件 return next(action); } let ACTION_TYPE = action["type"]; let { apiName, params = {} , opts = {} } = API_OPT; /** * 如果有傳遞localData,就不會(huì)觸發(fā)ajax了,直接觸發(fā)_success * 當(dāng)前也可以傳其他參數(shù) */ let { localData } = opts; let { onSuccess, onError, onProgress, ajaxType = "GET", param } = params; // 觸發(fā)下一個(gè)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 }; // 觸發(fā)正在請(qǐng)求的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 }; //觸發(fā)請(qǐng)求成功的action return next(nextAction(apiName + "_SUCCESS", params, opts)); }, error: data => { onError && onError(data); //觸發(fā)請(qǐng)求失敗的action return next(nextAction(apiName + "_ERROR", params, opts)); } }); return result; };
強(qiáng)調(diào)一點(diǎn):項(xiàng)目中全部的接口,相當(dāng)于一個(gè)關(guān)于異步的actionType有一個(gè)對(duì)應(yīng)的后端接口,所以我們才可以通過(guò)API_ROOT[apiName]找到這個(gè)接口
以cart為列子(下面是對(duì)應(yīng)的每個(gè)文件):
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:
//項(xiàng)目中使用redux的bindActionCreators做一個(gè)統(tǒng)一的綁定,所以在這里多帶帶引入 export { request } from "./request"; //下面是非異步的方法 export function cartSelect(id) { return { type: types.CART_MAIN_SELECT, id }; }
項(xiàng)目中發(fā)起結(jié)構(gòu)是這樣的:
let url = types.CART_MAIN_GET; let param = {}; let params = { param: param, ajaxType: "GET", onSuccess: (res) => { /*...*/ }, onError: (res) => { /*...*/ } }; request(url, params, {});
其對(duì)應(yīng)的reducers就是下面
import * as types from "../constants/actions/cart"; const initialState = { main:{ isFetching: 0,//是否已經(jīng)獲取 didInvalidate:1,//是否失效 itemArr:[],//自定義模版 itemObj:{},//自定義模版數(shù)據(jù) header:{}//頭部導(dǎo)航 } }; export default function(state = initialState, action) { let newState; switch (action.type) { case types.HOME_MAIN_GET + "_ON"://可以不寫(xiě) /*...*/ return newState; case types.HOME_MAIN_GET + "_SUCCESS": /*...*/ return newState; case types.HOME_MAIN_GET + "_ERROR"://可以不寫(xiě) /*...*/ return newState; default: return state; } };
異步,數(shù)據(jù)驗(yàn)證都可以通過(guò)中間件做處理;引用Generator,Async/Await,Promise處理,可以參考社區(qū)中的一些其他方式,比如:
redux-promise
redux-saga
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86545.html
摘要:希望大家在這浮夸的前端圈里,保持冷靜,堅(jiān)持每天花分鐘來(lái)學(xué)習(xí)與思考。 今天的React題沒(méi)有太多的故事…… 半個(gè)月前出了248個(gè)Vue的知識(shí)點(diǎn),受到很多朋友的關(guān)注,都強(qiáng)烈要求再出多些React相前的面試題,受到大家的邀請(qǐng),我又找了20多個(gè)React的使用者,他們給出了328道React的面試題,由我整理好發(fā)給大家,同時(shí)發(fā)布在了前端面試每日3+1的React專(zhuān)題,希望對(duì)大家有所幫助,同時(shí)大...
摘要:我們可以為元素添加屬性然后在回調(diào)函數(shù)中接受該元素在樹(shù)中的句柄,該值會(huì)作為回調(diào)函數(shù)的第一個(gè)參數(shù)返回。使用最常見(jiàn)的用法就是傳入一個(gè)對(duì)象。單向數(shù)據(jù)流,比較有序,有便于管理,它隨著視圖庫(kù)的開(kāi)發(fā)而被概念化。 面試中問(wèn)框架,經(jīng)常會(huì)問(wèn)到一些原理性的東西,明明一直在用,也知道怎么用, 但面試時(shí)卻答不上來(lái),也是挺尷尬的,就干脆把react相關(guān)的問(wèn)題查了下資料,再按自己的理解整理了下這些答案。 reac...
摘要:面試題來(lái)源于網(wǎng)絡(luò),看一下高級(jí)前端的面試題,可以知道自己和高級(jí)前端的差距。 面試題來(lái)源于網(wǎng)絡(luò),看一下高級(jí)前端的面試題,可以知道自己和高級(jí)前端的差距。有些面試題會(huì)重復(fù)。 使用過(guò)的koa2中間件 koa-body原理 介紹自己寫(xiě)過(guò)的中間件 有沒(méi)有涉及到Cluster 介紹pm2 master掛了的話(huà)pm2怎么處理 如何和MySQL進(jìn)行通信 React聲明周期及自己的理解 如何...
摘要:寫(xiě)在最前原文首發(fā)于作者的知乎專(zhuān)欄中間件思想遇見(jiàn)的靈感附,感興趣的同學(xué)可以知乎關(guān)注,進(jìn)行交流。其中,最重要的一個(gè)便是對(duì)多線(xiàn)程的支持。在中提出了工作線(xiàn)程的概念,并且規(guī)范出的三大主要特征能夠長(zhǎng)時(shí)間運(yùn)行響應(yīng)理想的啟動(dòng)性能以及理想的內(nèi)存消耗。 寫(xiě)在最前 原文首發(fā)于作者的知乎專(zhuān)欄:React Redux 中間件思想遇見(jiàn) Web Worker 的靈感(附demo),感興趣的同學(xué)可以知乎關(guān)注,進(jìn)行交流...
閱讀 2299·2021-11-10 11:35
閱讀 919·2021-09-26 09:55
閱讀 2411·2021-09-22 15:22
閱讀 2329·2021-09-22 15:17
閱讀 3707·2021-09-09 09:33
閱讀 1837·2019-08-30 11:22
閱讀 979·2019-08-30 10:57
閱讀 652·2019-08-29 16:10