摘要:如果想學習項目的底層建設,建議先去學習官網案例,之后在學習的使用中間件介紹目的是提供第三方插件的模式,改變的過程。
前言
React/Redux項目結束后,當我在研究react-router源碼的時候發現當中有一部分含中間件的思想,所以才想把中間件重新梳理一遍;在之前看redux了解到中間件,redux層面中間件的理解對項目前期比較有幫助,雖然項目中后期基本可以忽略這層概念;現在對這部分的筆記重新梳理,這里只針對這個中間件做一個理解。
Redux 中間件介紹如果想學習項目的底層建設,建議先去學習官網redux案例,之后在學習react-router的使用
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 } } }
//源碼分析 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; };
//源碼分析 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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86545.html
摘要:希望大家在這浮夸的前端圈里,保持冷靜,堅持每天花分鐘來學習與思考。 今天的React題沒有太多的故事…… 半個月前出了248個Vue的知識點,受到很多朋友的關注,都強烈要求再出多些React相前的面試題,受到大家的邀請,我又找了20多個React的使用者,他們給出了328道React的面試題,由我整理好發給大家,同時發布在了前端面試每日3+1的React專題,希望對大家有所幫助,同時大...
摘要:我們可以為元素添加屬性然后在回調函數中接受該元素在樹中的句柄,該值會作為回調函數的第一個參數返回。使用最常見的用法就是傳入一個對象。單向數據流,比較有序,有便于管理,它隨著視圖庫的開發而被概念化。 面試中問框架,經常會問到一些原理性的東西,明明一直在用,也知道怎么用, 但面試時卻答不上來,也是挺尷尬的,就干脆把react相關的問題查了下資料,再按自己的理解整理了下這些答案。 reac...
摘要:面試題來源于網絡,看一下高級前端的面試題,可以知道自己和高級前端的差距。 面試題來源于網絡,看一下高級前端的面試題,可以知道自己和高級前端的差距。有些面試題會重復。 使用過的koa2中間件 koa-body原理 介紹自己寫過的中間件 有沒有涉及到Cluster 介紹pm2 master掛了的話pm2怎么處理 如何和MySQL進行通信 React聲明周期及自己的理解 如何...
摘要:寫在最前原文首發于作者的知乎專欄中間件思想遇見的靈感附,感興趣的同學可以知乎關注,進行交流。其中,最重要的一個便是對多線程的支持。在中提出了工作線程的概念,并且規范出的三大主要特征能夠長時間運行響應理想的啟動性能以及理想的內存消耗。 寫在最前 原文首發于作者的知乎專欄:React Redux 中間件思想遇見 Web Worker 的靈感(附demo),感興趣的同學可以知乎關注,進行交流...
閱讀 2287·2021-11-10 11:35
閱讀 899·2021-09-26 09:55
閱讀 2388·2021-09-22 15:22
閱讀 2318·2021-09-22 15:17
閱讀 3683·2021-09-09 09:33
閱讀 1821·2019-08-30 11:22
閱讀 970·2019-08-30 10:57
閱讀 641·2019-08-29 16:10